@abtnode/docker-utils 1.16.38-beta-20250108-235929-17742885

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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2018-2025 ArcBlock
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/dist/index.cjs ADDED
@@ -0,0 +1,656 @@
1
+ 'use strict';
2
+
3
+ const Joi = require('joi');
4
+
5
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
6
+
7
+ const Joi__default = /*#__PURE__*/_interopDefaultCompat(Joi);
8
+
9
+ const whiteDockerArgs = {
10
+ // 短参数映射
11
+ "-a": "--attach",
12
+ // 将标准输入、输出或错误流附加到容器(通常与 -i -t 一起使用)
13
+ "-d": "--detach",
14
+ // 以后台模式运行容器(分离模式)
15
+ "-e": "--env",
16
+ // 设置环境变量(例如:-e "VAR=value")
17
+ "-h": "--hostname",
18
+ // 设置容器的主机名
19
+ "-i": "--interactive",
20
+ // 保持 STDIN 为打开状态(与 -t 一起用可进入交互式模式)
21
+ "-it": "-it",
22
+ "-rm": "-rm",
23
+ "-m": "--memory",
24
+ // 限制容器可使用的内存大小(例如:-m 512m)
25
+ "-p": "--publish",
26
+ // 将容器的端口映射到主机(例如:-p 8080:80)
27
+ "-P": "--publish-all",
28
+ // 将容器暴露的所有端口随机映射到主机端口
29
+ "-l": "--label",
30
+ // 为容器添加自定义元数据标签(例如:-l "key=value")
31
+ "-n": "--name",
32
+ // 为容器指定名称,而不是自动分配
33
+ "-q": "--quiet",
34
+ // 静默模式,不输出日志信息(通常用于 docker build)
35
+ "-x": "--x-registry",
36
+ // 非标准参数(此处为自定义场景)可用于指定镜像注册表等
37
+ "-t": "--tty",
38
+ // 分配一个伪 TTY(终端),常与 -i 一起使用
39
+ "-u": "--user",
40
+ // 指定以哪个用户或 UID 运行容器内的进程
41
+ "-v": "--volume",
42
+ // 挂载宿主机目录或数据卷到容器中
43
+ "-w": "--workdir",
44
+ // 设置容器内的工作目录
45
+ "-cgroupns": "--cgroupns",
46
+ // 设置 cgroup 命名空间的模式(host 或 private)
47
+ "-cpus": "--cpus",
48
+ // 限制可用给容器的 CPU 数量
49
+ "-g": "--gpus",
50
+ // 分配 GPU 给容器(需要适配 NVIDIA runtime)
51
+ "-ip6": "--ip6",
52
+ // 设置容器的 IPv6 地址
53
+ "--read-only": "--read-only",
54
+ // 将容器的文件系统设置为只读
55
+ // 长参数映射
56
+ "--add-host": "--add-host",
57
+ // 添加自定义主机名映射到 /etc/hosts 中(格式:host:IP)
58
+ "--attach": "--attach",
59
+ // 附加到容器的 STDIN/STDOUT/STDERR
60
+ "--blkio-weight": "--blkio-weight",
61
+ // 设置容器块 IO 相对权重(需要 cgroup 支持)
62
+ "--blkio-weight-device": "--blkio-weight-device",
63
+ // 针对指定设备设置块 IO 权重
64
+ "--cap-add": "--cap-add",
65
+ // 向容器添加 Linux 功能(Capabilities)
66
+ "--cap-drop": "--cap-drop",
67
+ // 从容器中移除特定 Linux 功能
68
+ "--cgroup-parent": "--cgroup-parent",
69
+ // 指定容器的 cgroup 父组
70
+ "--cidfile": "--cidfile",
71
+ // 将创建的容器 ID 写入到指定文件中
72
+ "--cpu-period": "--cpu-period",
73
+ // 限制 CPU CFS 调度周期
74
+ "--cpu-quota": "--cpu-quota",
75
+ // 限制 CPU CFS 配额,控制 CPU 时间的使用量
76
+ "--cpu-rt-period": "--cpu-rt-period",
77
+ // 调整 CPU 实时调度周期(需要特定权限)
78
+ "--cpu-rt-runtime": "--cpu-rt-runtime",
79
+ // 调整 CPU 实时调度运行时间(需要特定权限)
80
+ "--cpu-shares": "--cpu-shares",
81
+ // 设置 CPU 共享权重(相对值)
82
+ "--cpus": "--cpus",
83
+ // 限制容器可用 CPU 数量(例如:--cpus 2.5)
84
+ "--cpuset-cpus": "--cpuset-cpus",
85
+ // 指定容器能使用的 CPU 核心(例如:--cpuset-cpus="0-2,4")
86
+ "--cpuset-mems": "--cpuset-mems",
87
+ // 指定容器能使用的内存节点(NUMA 节点)
88
+ "--detach": "--detach",
89
+ // 后台运行容器
90
+ "--detach-keys": "--detach-keys",
91
+ // 设置分离容器的键组合
92
+ "--device": "--device",
93
+ // 将主机设备映射到容器(例如:--device=/dev/sda:/dev/xvda)
94
+ "--device-cgroup-rule": "--device-cgroup-rule",
95
+ // 为设备访问添加 cgroup 规则
96
+ "--device-read-bps": "--device-read-bps",
97
+ // 限制设备读取速率(字节/秒)
98
+ "--device-read-iops": "--device-read-iops",
99
+ // 限制设备读取 IOPS
100
+ "--device-write-bps": "--device-write-bps",
101
+ // 限制设备写入速率(字节/秒)
102
+ "--device-write-iops": "--device-write-iops",
103
+ // 限制设备写入 IOPS
104
+ "--disable-content-trust": "--disable-content-trust",
105
+ // 禁用镜像拉取时的内容信任校验
106
+ "--dns": "--dns",
107
+ // 设置容器使用的 DNS 服务器
108
+ "--dns-opt": "--dns-opt",
109
+ // 为 DNS 配置添加选项
110
+ "--dns-search": "--dns-search",
111
+ // 为 DNS 搜索域配置添加域名
112
+ "--domainname": "--domainname",
113
+ // 设置容器的域名(FQDN)
114
+ "--entrypoint": "--entrypoint",
115
+ // 覆盖镜像的默认 ENTRYPOINT
116
+ "--env": "--env",
117
+ // 设置环境变量
118
+ "--env-file": "--env-file",
119
+ // 从文件中读取环境变量
120
+ "--expose": "--expose",
121
+ // 声明容器将会监听的端口(不做主机端口映射,只是元数据声明)
122
+ "--gpus": "--gpus",
123
+ // 分配 GPU 给容器
124
+ "--group-add": "--group-add",
125
+ // 向容器添加更多用户组(GID)
126
+ "--health-cmd": "--health-cmd",
127
+ // 容器健康检查命令
128
+ "--health-interval": "--health-interval",
129
+ // 健康检查的时间间隔
130
+ "--health-retries": "--health-retries",
131
+ // 健康检查的重试次数
132
+ "--health-start-period": "--health-start-period",
133
+ // 健康检查开始前的初始化等待时间
134
+ "--health-timeout": "--health-timeout",
135
+ // 健康检查的超时时间
136
+ "--help": "--help",
137
+ // 显示帮助信息
138
+ "--hostname": "--hostname",
139
+ // 设置容器主机名
140
+ "--init": "--init",
141
+ // 在容器内使用 init 进程(帮助处理僵尸进程)
142
+ "--interactive": "--interactive",
143
+ // 保持 STDIN 打开
144
+ "--ip": "--ip",
145
+ // 指定容器的 IPv4 地址(需自定义网络)
146
+ "--ip6": "--ip6",
147
+ // 指定容器的 IPv6 地址(需自定义网络)
148
+ "--ipc": "--ipc",
149
+ // 设置容器的 IPC 命名空间(shareable, container:id, 或 host)
150
+ "--isolation": "--isolation",
151
+ // 指定容器隔离级别(Windows 环境下使用,如 process 或 hyperv)
152
+ "--kernel-memory": "--kernel-memory",
153
+ // 限制内核内存使用(已弃用)
154
+ "--label": "--label",
155
+ // 为容器添加自定义元数据标签
156
+ "--label-file": "--label-file",
157
+ // 从文件中读取标签
158
+ "--link": "--link",
159
+ // 连接到另一个容器的网络别名(已弃用,建议用网络替代)
160
+ "--link-local-ip": "--link-local-ip",
161
+ // 为容器分配一个链接本地地址(IPv4/IPv6)
162
+ "--log-driver": "--log-driver",
163
+ // 设置容器日志驱动(例如 json-file、syslog 等)
164
+ "--log-opt": "--log-opt",
165
+ // 为日志驱动添加额外配置选项
166
+ "--mac-address": "--mac-address",
167
+ // 设置容器的 MAC 地址(需自定义网络)
168
+ "--memory": "--memory",
169
+ // 限制容器可使用的内存
170
+ "--memory-reservation": "--memory-reservation",
171
+ // 设置内存软限制(触发内存管理)
172
+ "--memory-swap": "--memory-swap",
173
+ // 设置内存+swap 的限制值
174
+ "--memory-swappiness": "--memory-swappiness",
175
+ // 调整内存交换行为(0-100)
176
+ "--mount": "--mount",
177
+ // 使用高级语法挂载数据卷或绑定宿主机目录
178
+ "--name": "--name",
179
+ // 指定容器名称
180
+ "--network": "--network",
181
+ // 指定容器使用的网络(默认 bridge)
182
+ "--network-alias": "--network-alias",
183
+ // 为容器添加网络别名(在用户定义网络中)
184
+ "--no-healthcheck": "--no-healthcheck",
185
+ // 禁用继承自镜像的健康检查
186
+ "--oom-kill-disable": "--oom-kill-disable",
187
+ // 禁用内存不足时对容器进程的杀死操作
188
+ "--oom-score-adj": "--oom-score-adj",
189
+ // 调整容器的 OOM 分数(影响 OOM 杀死优先级)
190
+ "--pid": "--pid",
191
+ // 设置 PID 命名空间(容器与主机共享进程视图)
192
+ "--pids-limit": "--pids-limit",
193
+ // 限制容器内可用的进程数
194
+ "--platform": "--platform",
195
+ // 指定镜像平台(如 linux/amd64)
196
+ "--privileged": "--privileged",
197
+ // 提升容器为特权模式(访问更多主机资源)
198
+ "--publish": "--publish",
199
+ // 映射容器端口到宿主机
200
+ "--publish-all": "--publish-all",
201
+ // 自动映射容器暴露的所有端口到宿主机随机端口
202
+ "--pull": "--pull",
203
+ // 指定拉取镜像策略(always, missing, never)
204
+ "--restart": "--restart",
205
+ // 容器退出后的重启策略(no, on-failure, always, unless-stopped)
206
+ "--rm": "--rm",
207
+ // 容器停止后自动删除容器
208
+ "--runtime": "--runtime",
209
+ // 指定运行时(如 runc, nvidia-container-runtime)
210
+ "--security-opt": "--security-opt",
211
+ // 设置安全选项(如 seccomp, apparmor 配置)
212
+ "--shm-size": "--shm-size",
213
+ // 设置 /dev/shm 的大小(默认 64M)
214
+ "--stop-signal": "--stop-signal",
215
+ // 设置停止容器时发送的信号(默认 SIGTERM)
216
+ "--stop-timeout": "--stop-timeout",
217
+ // 停止容器时等待指定秒数后发送 SIGKILL
218
+ "--storage-opt": "--storage-opt",
219
+ // 设置存储驱动的选项(如 devicemapper)
220
+ "--sysctl": "--sysctl",
221
+ // 设置内核参数(sysctl)
222
+ "--tmpfs": "--tmpfs",
223
+ // 挂载一个 tmpfs 临时文件系统
224
+ "--tty": "--tty",
225
+ // 分配一个伪终端 TTY
226
+ "--ulimit": "--ulimit",
227
+ // 设置容器的 ulimit 限制(如打开文件数)
228
+ "--user": "--user",
229
+ // 指定容器内运行进程的用户/组
230
+ "--userns": "--userns",
231
+ // 设置用户命名空间模式(host 或 private)
232
+ "--uts": "--uts",
233
+ // 设置 UTS 命名空间(host 或 container)
234
+ "--volume": "--volume",
235
+ // 挂载宿主机目录或数据卷到容器中
236
+ "--volume-driver": "--volume-driver",
237
+ // 指定数据卷驱动
238
+ "--volumes-from": "--volumes-from",
239
+ // 从另一个容器挂载数据卷
240
+ "--workdir": "--workdir"
241
+ // 设置容器内的工作目录
242
+ };
243
+ const allowDockerArgs = {
244
+ "--env": true,
245
+ "--publish": true,
246
+ "--volume": true,
247
+ "--attach": true,
248
+ "--quiet": true,
249
+ // '--user': true,
250
+ "--workdir": true,
251
+ "--cidfile": true,
252
+ "--detach-keys": true,
253
+ "--disable-content-trust": true,
254
+ "--domainname": true,
255
+ "--expose": true,
256
+ "--ip": true,
257
+ "--link-local-ip": true,
258
+ "--platform": true
259
+ };
260
+ const autoSetDockerArgs = {
261
+ "-rm": true,
262
+ "--name": true,
263
+ "--label": true,
264
+ "--network": true,
265
+ "--user": true
266
+ };
267
+ const notAllowUseCustomDockerArgs = {
268
+ "--volume": true,
269
+ "--publish": true
270
+ };
271
+
272
+ const needValueKeys = /* @__PURE__ */ new Set(["--publish", "--volume"]);
273
+ function addBlockletPrefixVolume(args) {
274
+ return args.map((arg) => {
275
+ if (arg.key === "--volume" && !arg.value.startsWith("$BLOCKLET_APP_DIR/") && !arg.value.startsWith("$BLOCKLET_DATA_DIR/")) {
276
+ return {
277
+ ...arg,
278
+ value: arg.value.startsWith("/") ? `$BLOCKLET_DATA_DIR${arg.value}` : `$BLOCKLET_DATA_DIR/${arg.value}`
279
+ };
280
+ }
281
+ return arg;
282
+ });
283
+ }
284
+ function dockerParseCommand(command) {
285
+ if (!command) {
286
+ return { dockerArgs: [], dockerEnvs: [], dockerImage: "" };
287
+ }
288
+ command = command.trim();
289
+ if (command.startsWith("$ docker run")) {
290
+ command = command.replace("$ docker run", "docker run");
291
+ }
292
+ if (!command.startsWith("docker run")) {
293
+ return { dockerArgs: [], dockerEnvs: [], dockerImage: "" };
294
+ }
295
+ const cleanedCommand = command.replace(/\\\n/g, "").replace(/\n/g, " ").trim();
296
+ const parts = cleanedCommand.match(/(?:[^\s"']+|'[^']*'|"[^"]*")+/g);
297
+ if (!parts) {
298
+ return { dockerArgs: [], dockerEnvs: [], dockerImage: "" };
299
+ }
300
+ const dockerArgs = [];
301
+ const dockerEnvs = [];
302
+ let i = 0;
303
+ let dockerImage = "";
304
+ while (i < parts.length) {
305
+ const part = parts[i];
306
+ if (!part) {
307
+ i++;
308
+ continue;
309
+ }
310
+ if (part === "docker" || part === "run") {
311
+ i++;
312
+ continue;
313
+ }
314
+ if ((part === "-e" || part === "--env") && i + 1 < parts.length) {
315
+ const envString = parts[i + 1] || "";
316
+ const [envKey, ...envValueParts] = envString.split("=");
317
+ if (envKey) {
318
+ const envValue = envValueParts.join("=").replace(/^['"]|['"]$/g, "");
319
+ dockerEnvs.push({ key: envKey, value: envValue });
320
+ }
321
+ i += 2;
322
+ continue;
323
+ }
324
+ if (part.startsWith("-")) {
325
+ const equalIndex = part.indexOf("=");
326
+ let rawKey = part;
327
+ let value = null;
328
+ if (equalIndex !== -1) {
329
+ rawKey = part.substring(0, equalIndex);
330
+ value = part.substring(equalIndex + 1).replace(/^['"]|['"]$/g, "");
331
+ }
332
+ const fullKey = whiteDockerArgs[rawKey];
333
+ if (!fullKey) {
334
+ i++;
335
+ continue;
336
+ }
337
+ if (fullKey === "--publish") {
338
+ let publishValue = "";
339
+ if (value) {
340
+ publishValue = value;
341
+ dockerArgs.push({ key: fullKey, value: publishValue });
342
+ } else {
343
+ if (i + 1 < parts.length && !(parts[i + 1] || "").startsWith("-")) {
344
+ const nextVal = (parts[i + 1] || "").replace(/^['"]|['"]$/g, "");
345
+ publishValue = nextVal;
346
+ dockerArgs.push({ key: fullKey, value: publishValue });
347
+ i += 2;
348
+ continue;
349
+ } else {
350
+ dockerArgs.push({ key: fullKey, value: "true" });
351
+ i++;
352
+ continue;
353
+ }
354
+ }
355
+ i++;
356
+ continue;
357
+ }
358
+ if (value !== null) {
359
+ dockerArgs.push({ key: fullKey, value });
360
+ i++;
361
+ continue;
362
+ } else {
363
+ if (i + 1 < parts.length && !(parts[i + 1] || "").startsWith("-")) {
364
+ const nextVal = (parts[i + 1] || "").replace(/^['"]|['"]$/g, "");
365
+ dockerArgs.push({ key: fullKey, value: nextVal });
366
+ i += 2;
367
+ continue;
368
+ } else {
369
+ dockerArgs.push({ key: fullKey, value: "true" });
370
+ i++;
371
+ continue;
372
+ }
373
+ }
374
+ }
375
+ dockerImage = part.replace(/^['"]|['"]$/g, "");
376
+ i++;
377
+ }
378
+ const filteredDockerArgs = [];
379
+ for (const arg of dockerArgs) {
380
+ arg.name = "";
381
+ arg.path = "";
382
+ arg.prefix = "";
383
+ arg.protocol = "";
384
+ arg.type = "docker";
385
+ if (needValueKeys.has(arg.key) && arg.value === "true") {
386
+ continue;
387
+ }
388
+ filteredDockerArgs.push(arg);
389
+ }
390
+ for (const item of dockerEnvs) {
391
+ item.custom = "";
392
+ item.description = "";
393
+ item.secure = false;
394
+ item.shared = false;
395
+ item.required = false;
396
+ }
397
+ return { dockerArgs: addBlockletPrefixVolume(filteredDockerArgs), dockerEnvs, dockerImage };
398
+ }
399
+ function dockerBuildCommand({
400
+ dockerArgs,
401
+ dockerEnvs,
402
+ dockerImage
403
+ }) {
404
+ if (dockerArgs.length === 0 && dockerEnvs.length === 0 && !dockerImage) {
405
+ return "";
406
+ }
407
+ const parts = ["docker run \\"];
408
+ const multipleArgs = /* @__PURE__ */ new Set([
409
+ "--publish",
410
+ "--volume",
411
+ "--expose",
412
+ "--device",
413
+ "--mount",
414
+ "--label",
415
+ "--cap-add",
416
+ "--cap-drop",
417
+ "--add-host",
418
+ "--dns",
419
+ "--network-alias",
420
+ "--sysctl",
421
+ "--ulimit",
422
+ "--network"
423
+ ]);
424
+ for (const arg of dockerArgs) {
425
+ const { key, value } = arg;
426
+ if (multipleArgs.has(key)) {
427
+ if (value === "true") {
428
+ parts.push(` ${key} \\`);
429
+ } else {
430
+ const formattedValue = /[\s"']/g.test(value) ? `"${value}"` : value;
431
+ parts.push(` ${key} ${formattedValue} \\`);
432
+ }
433
+ } else {
434
+ if (!parts.some((part) => part.startsWith(` ${key}`))) {
435
+ if (value === "true") {
436
+ parts.push(` ${key} \\`);
437
+ } else {
438
+ const formattedValue = /[\s"']/g.test(value) ? `"${value}"` : value;
439
+ parts.push(` ${key} ${formattedValue} \\`);
440
+ }
441
+ }
442
+ }
443
+ }
444
+ for (const env of dockerEnvs) {
445
+ const { key, value } = env;
446
+ const formattedEnvValue = /["']/g.test(value) ? value : `"${value}"`;
447
+ parts.push(` -e ${key}=${formattedEnvValue} \\`);
448
+ }
449
+ if (dockerImage) {
450
+ parts.push(` ${dockerImage}`);
451
+ } else {
452
+ if (parts[parts.length - 1]?.endsWith("\\")) {
453
+ parts[parts.length - 1] = (parts[parts.length - 1] || "").slice(0, -1).trim();
454
+ }
455
+ }
456
+ return parts.join("\n");
457
+ }
458
+
459
+ const dockerArgsValidator = {
460
+ "--volume": (volume) => {
461
+ if (!volume.startsWith("$BLOCKLET_APP_DIR/") && !volume.startsWith("$BLOCKLET_DATA_DIR/")) {
462
+ return "Volume must start with $BLOCKLET_APP_DIR or $BLOCKLET_DATA_DIR";
463
+ }
464
+ if (volume.indexOf("..") > -1) {
465
+ return 'Volume cannot contain ".."';
466
+ }
467
+ return "";
468
+ }
469
+ };
470
+
471
+ const dockerArgsToCamelCase = (str) => {
472
+ const strippedStr = str.replace(/^[-]+/, "");
473
+ const camelCaseStr = strippedStr.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
474
+ return camelCaseStr;
475
+ };
476
+ const dockerCamelCaseToDash = (str) => {
477
+ const kebabCaseStr = str.replace(/([A-Z])/g, "-$1").toLowerCase();
478
+ return `--${kebabCaseStr}`;
479
+ };
480
+
481
+ const allowedDockerArgKeys = Array.from(new Set(Object.keys(allowDockerArgs).map(dockerArgsToCamelCase)));
482
+ const multiValueArgs = ["publish", "volume"];
483
+ const dockerSchema = Joi__default.object({
484
+ workdir: Joi__default.string().trim().optional(),
485
+ image: Joi__default.string().trim().regex(/^[^\s]+$/).optional(),
486
+ shell: Joi__default.string().trim().optional(),
487
+ // network: Joi.string().valid('host', 'bridge', 'none').default('host').optional(),
488
+ volumes: Joi__default.array().items(Joi__default.string().trim()).optional(),
489
+ script: Joi__default.string().trim().optional(),
490
+ installNodeModules: Joi__default.boolean().optional(),
491
+ command: Joi__default.string().trim().optional(),
492
+ runBaseScript: Joi__default.boolean().optional(),
493
+ // 动态 Docker 参数
494
+ ...allowedDockerArgKeys.reduce(
495
+ (acc, key) => {
496
+ const validFn = dockerArgsValidator[dockerCamelCaseToDash(key)];
497
+ if (multiValueArgs.includes(key)) {
498
+ acc[key] = Joi__default.array().items(Joi__default.string().trim()).optional();
499
+ if (validFn) {
500
+ acc[key] = Joi__default.array().items(
501
+ Joi__default.string().trim().custom((value, helper) => {
502
+ const msg = validFn(value);
503
+ if (msg) {
504
+ return helper.message(msg);
505
+ }
506
+ return value;
507
+ })
508
+ // eslint-disable-line @typescript-eslint/comma-dangle
509
+ ).optional();
510
+ } else {
511
+ acc[key] = Joi__default.array().items(Joi__default.string().trim()).optional();
512
+ }
513
+ return acc;
514
+ }
515
+ if (validFn) {
516
+ acc[key] = Joi__default.string().trim().custom((value, helper) => {
517
+ const msg = validFn(value);
518
+ if (msg) {
519
+ return helper.message(msg);
520
+ }
521
+ return value;
522
+ }).optional();
523
+ } else {
524
+ acc[key] = Joi__default.string().trim().optional();
525
+ }
526
+ return acc;
527
+ },
528
+ {}
529
+ // eslint-disable-line @typescript-eslint/comma-dangle
530
+ )
531
+ }).optional();
532
+ const validateDockerImage = (dockerImage) => {
533
+ const reg = /^(?:[a-zA-Z0-9.-]+(?::[0-9]+)?\/)?(?:[a-z0-9]+(?:[._-][a-z0-9]+)*\/)?[a-z0-9]+(?:[._-][a-z0-9]+)*(:[a-zA-Z0-9_.-]+)?$/;
534
+ return Joi__default.string().trim().regex(reg).required().validate(dockerImage);
535
+ };
536
+ const parseDockerArgsToSchema = (dockerImage, args) => {
537
+ const obj = {};
538
+ args.forEach((item) => {
539
+ const key = dockerArgsToCamelCase(item.key);
540
+ if (multiValueArgs.includes(key)) {
541
+ obj[key] = [...obj[key] || [], item.value];
542
+ } else {
543
+ obj[key] = item.value;
544
+ }
545
+ });
546
+ const { error } = validateDockerImage(dockerImage);
547
+ if (error) {
548
+ return { error, value: obj };
549
+ }
550
+ obj.image = dockerImage;
551
+ return dockerSchema.validate(obj);
552
+ };
553
+
554
+ function dockerParsePublishPorts(dockerImage, dockerArgs) {
555
+ const updatedDockerArgs = JSON.parse(JSON.stringify(dockerArgs));
556
+ const hostPortMap = /* @__PURE__ */ new Map();
557
+ let blockletPortIndex = 1;
558
+ const publishPorts = [];
559
+ updatedDockerArgs.forEach((arg) => {
560
+ if (arg.key === "--publish") {
561
+ const publishValue = arg.value;
562
+ const parts = publishValue.split(":");
563
+ let hostIP = "127.0.0.1";
564
+ let hostPort = "";
565
+ let containerPort = "";
566
+ if (parts.length === 2) {
567
+ [hostPort, containerPort] = parts;
568
+ } else if (parts.length === 3) {
569
+ [hostIP, hostPort, containerPort] = parts;
570
+ } else if (parts.length === 1) {
571
+ containerPort = parts[0] || "";
572
+ }
573
+ let blockletPort = hostPortMap.get(hostPort || "");
574
+ if (!blockletPort) {
575
+ blockletPort = `$BLOCKLET_PORT_${blockletPortIndex++}`;
576
+ hostPortMap.set(hostPort || "", blockletPort);
577
+ }
578
+ if (!publishPorts.find((port) => port.port === hostPort)) {
579
+ publishPorts.push({
580
+ type: publishPorts.length === 0 ? "web" : "docker",
581
+ name: `${dockerImage}-${publishPorts.length + 1}`,
582
+ path: `/${dockerImage}-${publishPorts.length + 1}`,
583
+ port: hostPort || containerPort,
584
+ // 若未指定 hostPort,则使用 containerPort
585
+ containerPort: Number(containerPort) || 0,
586
+ hostIP
587
+ });
588
+ }
589
+ }
590
+ });
591
+ return publishPorts;
592
+ }
593
+
594
+ function dockerParseEnvironments(envs, ignoreCustom = false) {
595
+ const blockletEnvs = [];
596
+ if (!envs) {
597
+ return blockletEnvs;
598
+ }
599
+ for (const env of envs) {
600
+ const item = {
601
+ name: env.key,
602
+ default: env.value === `$${env.key}` ? "" : env.value,
603
+ secure: env.secure !== void 0 ? env.secure : false,
604
+ shared: env.shared !== void 0 ? env.shared : false,
605
+ required: env.required !== void 0 ? env.required : false,
606
+ description: env.description || env.key
607
+ };
608
+ if (!ignoreCustom) {
609
+ item.custom = env.custom || "";
610
+ }
611
+ blockletEnvs.push(item);
612
+ }
613
+ return blockletEnvs;
614
+ }
615
+
616
+ function getObjByPath(data, path) {
617
+ const obj = JSON.parse(JSON.stringify(data));
618
+ const keys = path.split(".").map((key) => {
619
+ const match = key.match(/(.+)\[(.+?)\]/);
620
+ return match ? [match[1], match[2]] : key;
621
+ }).flat();
622
+ return keys.filter(Boolean).reduce((acc, key) => {
623
+ if (!acc || !key) {
624
+ return void 0;
625
+ }
626
+ const conditionMatch = key?.match(/(.+?)=(.+)/);
627
+ if (Array.isArray(acc) && conditionMatch) {
628
+ const [, field, value] = conditionMatch;
629
+ if (field && value) {
630
+ acc = acc.find((item) => {
631
+ const lowerField = Object.keys(item).find((k) => k.toLowerCase() === field.toLowerCase());
632
+ return lowerField && item[lowerField] === value;
633
+ });
634
+ }
635
+ return acc;
636
+ }
637
+ const lowerKey = Object.keys(acc).find((k) => k.toLowerCase() === key.toLowerCase());
638
+ return lowerKey ? acc[lowerKey] : void 0;
639
+ }, obj);
640
+ }
641
+
642
+ exports.allowDockerArgs = allowDockerArgs;
643
+ exports.autoSetDockerArgs = autoSetDockerArgs;
644
+ exports.dockerArgsToCamelCase = dockerArgsToCamelCase;
645
+ exports.dockerArgsValidator = dockerArgsValidator;
646
+ exports.dockerBuildCommand = dockerBuildCommand;
647
+ exports.dockerCamelCaseToDash = dockerCamelCaseToDash;
648
+ exports.dockerParseCommand = dockerParseCommand;
649
+ exports.dockerParseEnvironments = dockerParseEnvironments;
650
+ exports.dockerParsePublishPorts = dockerParsePublishPorts;
651
+ exports.dockerSchema = dockerSchema;
652
+ exports.getObjByPath = getObjByPath;
653
+ exports.multiValueArgs = multiValueArgs;
654
+ exports.notAllowUseCustomDockerArgs = notAllowUseCustomDockerArgs;
655
+ exports.parseDockerArgsToSchema = parseDockerArgsToSchema;
656
+ exports.whiteDockerArgs = whiteDockerArgs;