node-automator 1.4.30 → 1.4.32

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/biome.json CHANGED
@@ -1,63 +1,64 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
3
- "vcs": {
4
- "enabled": false,
5
- "clientKind": "git",
6
- "useIgnoreFile": false
7
- },
8
- "files": {
9
- "ignoreUnknown": false
10
- },
11
- "formatter": {
12
- "enabled": true,
13
- "indentStyle": "space",
14
- "indentWidth": 4
15
- },
16
- "linter": {
17
- "enabled": true,
18
- "rules": {
19
- "recommended": true,
20
- "complexity": {
21
- "noStaticOnlyClass": "off"
22
- },
23
- "suspicious": {
24
- "noDuplicateClassMembers": "warn",
25
- "useIterableCallbackReturn": "warn",
26
- "noCompareNegZero": "warn",
27
- "noDuplicateElseIf": "warn",
28
- "noSelfCompare": "warn",
29
- "noFallthroughSwitchClause": "warn",
30
- "noDebugger": "warn",
31
- "noShadowRestrictedNames": "warn",
32
- "noImplicitAnyLet": "warn",
33
- "noAssignInExpressions": "warn",
34
- "noEmptyInterface": "off",
35
- "noDuplicateObjectKeys": "warn",
36
- "noDoubleEquals": "warn",
37
- "noRedeclare": "warn"
38
- },
39
- "correctness": {
40
- "noPrecisionLoss": "warn",
41
- "noSwitchDeclarations": "warn",
42
- "noConstantCondition": "warn",
43
- "noUnreachable": "warn",
44
- "noInvalidUseBeforeDeclaration": "warn",
45
- "noInnerDeclarations": "warn",
46
- "noSelfAssign": "warn"
47
- }
48
- }
49
- },
50
- "javascript": {
51
- "formatter": {
52
- "quoteStyle": "double"
53
- }
54
- },
55
- "assist": {
56
- "enabled": true,
57
- "actions": {
58
- "source": {
59
- "organizeImports": "on"
60
- }
61
- }
62
- }
2
+ "$schema": "https://gitee.com/TsubasaYeung/public_resource/raw/master/schemas/biome/2.2.4/schema.json",
3
+ "vcs": {
4
+ "enabled": false,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": false
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false
10
+ },
11
+ "formatter": {
12
+ "enabled": true,
13
+ "indentStyle": "space",
14
+ "indentWidth": 4
15
+ },
16
+ "linter": {
17
+ "enabled": true,
18
+ "rules": {
19
+ "recommended": true,
20
+ "complexity": {
21
+ "noStaticOnlyClass": "off"
22
+ },
23
+ "suspicious": {
24
+ "noPrototypeBuiltins": "off",
25
+ "noDuplicateClassMembers": "warn",
26
+ "useIterableCallbackReturn": "warn",
27
+ "noCompareNegZero": "warn",
28
+ "noDuplicateElseIf": "warn",
29
+ "noSelfCompare": "warn",
30
+ "noFallthroughSwitchClause": "warn",
31
+ "noDebugger": "warn",
32
+ "noShadowRestrictedNames": "warn",
33
+ "noImplicitAnyLet": "warn",
34
+ "noAssignInExpressions": "warn",
35
+ "noEmptyInterface": "off",
36
+ "noDuplicateObjectKeys": "warn",
37
+ "noDoubleEquals": "warn",
38
+ "noRedeclare": "warn"
39
+ },
40
+ "correctness": {
41
+ "noPrecisionLoss": "warn",
42
+ "noSwitchDeclarations": "warn",
43
+ "noConstantCondition": "warn",
44
+ "noUnreachable": "warn",
45
+ "noInvalidUseBeforeDeclaration": "warn",
46
+ "noInnerDeclarations": "warn",
47
+ "noSelfAssign": "warn"
48
+ }
49
+ }
50
+ },
51
+ "javascript": {
52
+ "formatter": {
53
+ "quoteStyle": "double"
54
+ }
55
+ },
56
+ "assist": {
57
+ "enabled": true,
58
+ "actions": {
59
+ "source": {
60
+ "organizeImports": "on"
61
+ }
62
+ }
63
+ }
63
64
  }
@@ -1,13 +1,12 @@
1
1
  const {
2
2
  get_file_list,
3
- get_full_path,
4
- format_bytes,
5
3
  } = require("../utils/file_tool");
6
- const { success, getPrint, info, warn, whisper } = require("../utils/log_tool");
4
+ const { get_full_path } = require('../utils/transform_tool');
5
+ const { info, warn, whisper } = require("../utils/log_tool");
7
6
  const { BaseCommand } = require("./base");
8
7
  const path = require("node:path");
9
8
  const { progress } = require("../utils/display_tool");
10
- const { createWriteStream, readFileSync } = require("node:fs");
9
+ const { readFileSync } = require("node:fs");
11
10
  const { getCache, setCache } = require("../utils/cache_tool");
12
11
  const crypto = require("node:crypto");
13
12
  const { queueAsync } = require("../utils/func_tool");
@@ -138,12 +137,12 @@ class AliOssCommand extends BaseCommand {
138
137
  srcs = srcs.filter((src) => {
139
138
  const cachedValue =
140
139
  cachedFiles[
141
- path
142
- .join(
143
- dst_folder,
144
- path.relative(base, src),
145
- )
146
- .replace(/\\/g, "/")
140
+ path
141
+ .join(
142
+ dst_folder,
143
+ path.relative(base, src),
144
+ )
145
+ .replace(/\\/g, "/")
147
146
  ];
148
147
  switch (strategy) {
149
148
  case "lazy": {
@@ -5,9 +5,10 @@ const fs = require("node:fs");
5
5
  const path = require("node:path");
6
6
  const { success, error, warn } = require("../utils/log_tool");
7
7
  const { parse, stringify, processRename } = require("../utils/file_tool");
8
- const { eval_code, get_full_path } = require("./share_data");
8
+ const { eval_code } = require("./share_data");
9
9
  const { formatByteSize } = require("../utils/transform_tool");
10
10
  const { debounce, throttle } = require("../utils/func_tool");
11
+ const { get_full_path } = require('../utils/transform_tool');
11
12
 
12
13
  class BackupCommand extends BaseCommand {
13
14
  async execute() {
@@ -42,10 +43,10 @@ class BackupCommand extends BaseCommand {
42
43
  );
43
44
  const backName = backupFilenameGenerator
44
45
  ? eval_code(backupFilenameGenerator)({
45
- filepath,
46
- fileSize,
47
- fileSizeFormatted,
48
- })
46
+ filepath,
47
+ fileSize,
48
+ fileSizeFormatted,
49
+ })
49
50
  : backupFilename || filename;
50
51
  let backupPath = path.join(backupFolder, backName);
51
52
  let backupData;
package/commands/base.js CHANGED
@@ -5,7 +5,8 @@ const {
5
5
  flatten,
6
6
  removeDuplicate,
7
7
  } = require("../utils/transform_tool");
8
- const { parse, get_full_path, write_with_type } = require("../utils/file_tool");
8
+ const { parse, write_with_type } = require("../utils/file_tool");
9
+ const { get_full_path } = require('../utils/transform_tool');
9
10
 
10
11
  const { eval_code } = require("./share_data");
11
12
 
@@ -33,7 +34,7 @@ class BaseCommand {
33
34
  return true;
34
35
  }
35
36
 
36
- async didExecute() {}
37
+ async didExecute() { }
37
38
 
38
39
  async checkValid(commandCfg) {
39
40
  const conditionCode = commandCfg.when;
@@ -324,7 +325,7 @@ class BaseCommand {
324
325
  return ret;
325
326
  }
326
327
 
327
- async execute() {}
328
+ async execute() { }
328
329
 
329
330
  /**
330
331
  * 转交给另外一个类型的命令处理
@@ -349,6 +350,10 @@ class BaseCommand {
349
350
  return false;
350
351
  }
351
352
 
353
+ getRequireContentOptions() {
354
+ return {};
355
+ }
356
+
352
357
  getDataByKey() {
353
358
  return getDataByKey.apply(this, arguments);
354
359
  }
@@ -0,0 +1,179 @@
1
+ const { get_fst_file } = require("../utils/file_tool");
2
+ const { BaseCommand } = require("./base");
3
+ const sharp = require("sharp");
4
+ const { eval_code } = require("./share_data");
5
+
6
+ class BorderCommand extends BaseCommand {
7
+ async execute() {
8
+ const selfData = this.selfData;
9
+ const defaultBorderChecker = (pixel) => {
10
+ return true;
11
+ };
12
+ const {
13
+ src,
14
+ borderChecker: borderCheckerStr,
15
+ tolerance = 0,
16
+ } = selfData;
17
+ const borderChecker = borderCheckerStr
18
+ ? eval_code(borderCheckerStr)
19
+ : defaultBorderChecker;
20
+ const inputPath = get_fst_file(src);
21
+
22
+ // 读取图片并获取原始信息
23
+ const image = sharp(inputPath);
24
+ // 确保图片有alpha通道
25
+ const processedImage = image.ensureAlpha();
26
+ const { data, info } = await processedImage
27
+ .raw()
28
+ .toBuffer({ resolveWithObject: true });
29
+
30
+ // 检测裁剪边界
31
+ const bounds = this.detectTrimBounds(
32
+ data,
33
+ info.width,
34
+ info.height,
35
+ 4,
36
+ // info.channels,
37
+ borderChecker,
38
+ tolerance,
39
+ );
40
+ return {
41
+ x: bounds.left,
42
+ y: bounds.top,
43
+ w: bounds.width,
44
+ h: bounds.height,
45
+ };
46
+ }
47
+
48
+ /**
49
+ * 检查两个颜色是否在容差范围内匹配
50
+ * @param {Array<number>} color1 颜色1
51
+ * @param {Array<number>} color2 颜色2
52
+ * @returns {boolean} 是否匹配
53
+ */
54
+ isColorMatch(color1, color2, tolerance) {
55
+ if (color1[3] <= tolerance && color2[3] <= tolerance) {
56
+ return true;
57
+ }
58
+ for (let i = 0; i < 3; i++) {
59
+ if (Math.abs(color1[i] - color2[i]) > tolerance) {
60
+ return false;
61
+ }
62
+ }
63
+ return true;
64
+ }
65
+
66
+ detectTrimBounds(
67
+ pixelData,
68
+ width,
69
+ height,
70
+ channels,
71
+ borderChecker,
72
+ tolerance,
73
+ ) {
74
+ let left = 0;
75
+ let right = width - 1;
76
+ let top = 0;
77
+ let bottom = height - 1;
78
+
79
+ // 提取像素检查逻辑
80
+ const checkPixel = (x, y, refPixel) => {
81
+ const idx = (y * width + x) * channels;
82
+ const pixel = [
83
+ pixelData[idx],
84
+ pixelData[idx + 1],
85
+ pixelData[idx + 2],
86
+ channels === 4 ? pixelData[idx + 3] : 255,
87
+ ];
88
+
89
+ if (!borderChecker(pixel)) {
90
+ return { hasContent: true, pixel };
91
+ }
92
+
93
+ if (refPixel && !this.isColorMatch(refPixel, pixel, tolerance)) {
94
+ return { hasContent: true, pixel };
95
+ }
96
+
97
+ return { hasContent: false, pixel };
98
+ };
99
+
100
+ // 检测左边框
101
+ left = this.detectBoundary({
102
+ width, height,
103
+ startX: 0, startY: 0,
104
+ xIncrement: 1, yIncrement: 1,
105
+ isVertical: true,
106
+ checkPixel
107
+ });
108
+
109
+ // 检测右边框
110
+ right = this.detectBoundary({
111
+ width, height,
112
+ startX: width - 1, startY: 0,
113
+ xIncrement: -1, yIncrement: 1,
114
+ isVertical: true,
115
+ checkPixel
116
+ });
117
+
118
+ // 检测上边框
119
+ top = this.detectBoundary({
120
+ width, height,
121
+ startX: 0, startY: 0,
122
+ xIncrement: 1, yIncrement: 1,
123
+ isVertical: false,
124
+ checkPixel
125
+ });
126
+
127
+ // 检测下边框
128
+ bottom = this.detectBoundary({
129
+ width, height,
130
+ startX: 0, startY: height - 1,
131
+ xIncrement: 1, yIncrement: -1,
132
+ isVertical: false,
133
+ checkPixel
134
+ });
135
+
136
+ return {
137
+ left,
138
+ top,
139
+ width: right - left + 1,
140
+ height: bottom - top + 1,
141
+ };
142
+ }
143
+
144
+ // 单独提取的检测函数
145
+ detectBoundary({ width, height, startX, startY, xIncrement, yIncrement, isVertical, checkPixel }) {
146
+ let refPixel = null;
147
+ const maxX = isVertical ? width : height;
148
+ const maxY = isVertical ? height : width;
149
+
150
+ for (let i = 0; i < maxX; i++) {
151
+ let hasContent = false;
152
+
153
+ for (let j = 0; j < maxY; j++) {
154
+ const x = isVertical ? startX + xIncrement * i : j;
155
+ const y = isVertical ? j : startY + yIncrement * i;
156
+
157
+ const result = checkPixel(x, y, refPixel);
158
+ if (result.hasContent) {
159
+ hasContent = true;
160
+ break;
161
+ }
162
+
163
+ if (refPixel == null) {
164
+ refPixel = result.pixel;
165
+ }
166
+ }
167
+
168
+ if (hasContent) {
169
+ return isVertical ? startX + xIncrement * i : startY + yIncrement * i;
170
+ }
171
+ }
172
+
173
+ return isVertical ? startX + xIncrement * (maxX - 1) : startY + yIncrement * (maxY - 1);
174
+ }
175
+ }
176
+
177
+ module.exports = {
178
+ BorderCommand,
179
+ };
@@ -1,16 +1,21 @@
1
1
  const _path = require("node:path");
2
2
  const fs = require("node:fs");
3
- const { get_fst_file, get_full_path, move } = require("../utils/file_tool");
3
+ const { get_fst_file } = require("../utils/file_tool");
4
4
  const { BaseCommand } = require("./base");
5
5
  const { default: imagemin } = require("imagemin");
6
6
  const { default: imageminPngquant } = require("imagemin-pngquant");
7
7
  const { default: imageminMozjpeg } = require("imagemin-mozjpeg");
8
8
  const Jimp = require("jimp");
9
+ const { get_full_path, format_bytes } = require('../utils/transform_tool');
9
10
 
10
11
  class CompressCommand extends BaseCommand {
11
12
  async execute() {
12
13
  const data = this.selfData;
13
14
  const options = { quality: 80, ...data.options };
15
+ // 获取压缩前的文件大小
16
+ const src = get_fst_file(data.src);
17
+ const srcStat = fs.statSync(src);
18
+ const srcSize = srcStat.size;
14
19
  if (data.mode === "sharp") {
15
20
  const sharp = require("sharp");
16
21
  const data = this.selfData;
@@ -25,7 +30,11 @@ class CompressCommand extends BaseCommand {
25
30
  format = "jpeg";
26
31
  }
27
32
  const fileContent = fs.readFileSync(src);
28
- await sharp(fileContent)[format](options).toFile(dst);
33
+ let tmp = await sharp(fileContent)[format](options);
34
+ if (data.trim) {
35
+ tmp = tmp.trim();
36
+ }
37
+ await tmp.toFile(dst);
29
38
  } else {
30
39
  const src = get_fst_file(data.src);
31
40
  const dstFile = get_full_path(data.dst, "FILE");
@@ -56,6 +65,25 @@ class CompressCommand extends BaseCommand {
56
65
  ],
57
66
  });
58
67
  }
68
+ const dst = get_full_path(data.dst, "FILE");
69
+ const dstStat = fs.statSync(dst);
70
+ const dstSize = dstStat.size;
71
+ const sizeChanged = dstSize - srcSize;
72
+ const srcSizeFormated = format_bytes(srcSize);
73
+ const dstSizeFormated = format_bytes(dstSize);
74
+ const sizeRatioChanged = (1 - dstSize / srcSize);
75
+ const sizeRatioChangedFormated = `${(sizeRatioChanged * 100 * -1).toFixed(2)}%`;
76
+ const sizeChangedFormated = format_bytes(sizeChanged);
77
+ return {
78
+ srcSize,
79
+ srcSizeFormated,
80
+ dstSize,
81
+ dstSizeFormated,
82
+ sizeRatioChanged,
83
+ sizeRatioChangedFormated,
84
+ sizeChanged,
85
+ sizeChangedFormated,
86
+ }
59
87
  }
60
88
  }
61
89
 
@@ -1,15 +1,18 @@
1
+ const { readFileSync } = require('node:fs');
1
2
  const { BaseCommand } = require("./base");
2
3
  const crypto = require('node:crypto');
4
+ const { get_fst_file } = require('../utils/file_tool');
3
5
 
4
6
  class DecryptCommand extends BaseCommand {
5
7
  async execute() {
8
+ const data = this.selfData;
6
9
  const content = this.content;
7
- const { password, salt, algorithm = 'aes-256-cbc', keylen = 32 } = this.selfData;
8
- const key = crypto.scryptSync(password, salt, keylen);
9
- // Buffer 中提取 IV(前 16 字节)和密文
10
- const iv = content.slice(0, 16);
10
+ const { password, algorithm = 'aes-256-cbc', keyLen = 32, ivLen = 16, saltLen = 16 } = data;
11
+ const salt = content.subarray(0, saltLen);
12
+ const iv = content.subarray(saltLen, saltLen + ivLen);
13
+ const key = crypto.scryptSync(password, salt, keyLen);
11
14
  const decipher = crypto.createDecipheriv(algorithm, key, iv);
12
- let decrypted = decipher.update(content.slice(16)); // Buffer
15
+ let decrypted = decipher.update(content.subarray(saltLen + ivLen)); // 使用 .subarray()
13
16
  decrypted = Buffer.concat([decrypted, decipher.final()]);
14
17
  return decrypted;
15
18
  }
@@ -17,6 +20,12 @@ class DecryptCommand extends BaseCommand {
17
20
  getRequireContent() {
18
21
  return true;
19
22
  }
23
+
24
+ getRequireContentOptions() {
25
+ return {
26
+ raw: true
27
+ };
28
+ }
20
29
  }
21
30
 
22
31
  module.exports = {
@@ -4,12 +4,14 @@ const crypto = require('node:crypto');
4
4
  class EncryptCommand extends BaseCommand {
5
5
  async execute() {
6
6
  const content = this.content;
7
- const { password, salt, algorithm = 'aes-256-cbc', keylen = 32 } = this.selfData;
8
- const key = crypto.scryptSync(password, salt, keylen);
9
- const iv = crypto.randomBytes(16);
7
+ const { password, algorithm = 'aes-256-cbc', keyLen = 32, ivLen = 16, saltLen = 16 } = this.selfData;
8
+ const salt = crypto.randomBytes(saltLen);
9
+ const iv = crypto.randomBytes(ivLen);
10
+ const key = crypto.scryptSync(password, salt, keyLen);
10
11
  const cipher = crypto.createCipheriv(algorithm, key, iv);
11
12
  // 直接输出二进制 Buffer
12
13
  const encrypted = Buffer.concat([
14
+ salt, // 将盐值放在密文前面,方便解密时使用
13
15
  iv, // 将 IV 放在密文前面,方便解密时使用
14
16
  cipher.update(content), // 不指定输出编码 = 返回 Buffer
15
17
  cipher.final() // 返回 Buffer
@@ -20,6 +22,12 @@ class EncryptCommand extends BaseCommand {
20
22
  getRequireContent() {
21
23
  return true;
22
24
  }
25
+
26
+ getRequireContentOptions() {
27
+ return {
28
+ raw: true
29
+ };
30
+ }
23
31
  }
24
32
 
25
33
  module.exports = {
@@ -35,7 +35,11 @@ class EnvOptionalCommand extends BaseCommand {
35
35
  break;
36
36
  }
37
37
  case "object": {
38
- updatedData[k] = await parse(envVal, "yaml");
38
+ if (typeof envVal === "string") {
39
+ updatedData[k] = await parse(envVal, "yaml");
40
+ } else {
41
+ updatedData[k] = envVal;
42
+ }
39
43
  break;
40
44
  }
41
45
  default: {
@@ -17,6 +17,7 @@ class ExecFromFileCommand extends BaseCommand {
17
17
  async execute() {
18
18
  const data = this.selfData;
19
19
  const srcs = toArray(data.src);
20
+ const quiet = data.quiet || false;
20
21
  for (let i = 0; i < srcs.length; i++) {
21
22
  let src = srcs[i];
22
23
  const isRemote = /^https?:\/\//.test(src);
@@ -27,11 +28,12 @@ class ExecFromFileCommand extends BaseCommand {
27
28
  throw `配置文件未找到 ${srcs[i]}`;
28
29
  }
29
30
  if (!data.force && this.globalData.executed_cfg.includes(src)) {
30
- info(`不执行重复配置 ${src}`);
31
+ !quiet && info(`不执行重复配置 ${src}`);
31
32
  continue;
32
33
  }
33
- info(`执行配置文件 ${src}`);
34
+ !quiet && info(`执行配置文件 ${src}`);
34
35
  this.globalData.executed_cfg.push(src);
36
+ this.shareData.AUTOMATOR_EXEC_FROM_FILE = src;
35
37
  setLastExecFile(src);
36
38
  let cfg;
37
39
  if (isRemote) {
@@ -0,0 +1,35 @@
1
+ const { BaseCommand } = require("./base");
2
+ const walk = require('ignore-walk');
3
+ const { join } = require('node:path');
4
+ const { get_full_path } = require('../utils/transform_tool');
5
+
6
+ class FilesGitCommand extends BaseCommand {
7
+ async execute() {
8
+ const data = this.selfData;
9
+ const path = get_full_path(data.src);
10
+ const ignoreFiles = data.ignoreFiles ?? ['.gitignore'];
11
+ const includeEmpty = data.includeEmpty ?? false;
12
+ const includeDotGit = data.includeDotGit ?? false;
13
+ const follow = data.follow ?? false;
14
+ const full = data.full ?? true;
15
+ let files = walk.sync({
16
+ path, // root dir to start in. defaults to process.cwd()
17
+ ignoreFiles, // list of filenames. defaults to ['.ignore']
18
+ includeEmpty, // true to include empty dirs, default false
19
+ follow // true to follow symlink dirs, default false
20
+ });
21
+ if (!includeDotGit) {
22
+ files = files.filter(v => v !== '.git' && !v.startsWith('.git/'));
23
+ }
24
+ if (full) {
25
+ files = files.map(v => {
26
+ return join(path, v)
27
+ })
28
+ }
29
+ return files;
30
+ }
31
+ }
32
+
33
+ module.exports = {
34
+ FilesGitCommand,
35
+ };
@@ -0,0 +1,32 @@
1
+ const { basename, dirname } = require('node:path');
2
+ const { get_fst_file } = require('../utils/file_tool');
3
+ const { BaseCommand } = require("./base");
4
+ const { get_full_path } = require('../utils/transform_tool');
5
+ const Fontmin = require("fontmin").default;
6
+
7
+ class FontminCommand extends BaseCommand {
8
+ async execute() {
9
+ const result = await new Promise((resolve, reject) => {
10
+ const data = this.selfData;
11
+ const src = get_fst_file(data.src);
12
+ const options = { hinting: false, ...data.options };
13
+ const fontmin = new Fontmin()
14
+ .use(Fontmin.glyph({
15
+ text: options.text,
16
+ ...options
17
+ }))
18
+ fontmin.src(src).run((err, files) => {
19
+ if (err) {
20
+ reject(err);
21
+ } else {
22
+ resolve(files[0].contents);
23
+ }
24
+ });
25
+ })
26
+ return result;
27
+ }
28
+ }
29
+
30
+ module.exports = {
31
+ FontminCommand,
32
+ };
package/commands/ftp.js CHANGED
@@ -2,7 +2,8 @@ const { BaseCommand } = require("./base");
2
2
 
3
3
  const fs = require("node:fs");
4
4
  const path = require("node:path");
5
- const { get_full_path, get_file_list } = require("../utils/file_tool");
5
+ const { get_file_list } = require("../utils/file_tool");
6
+ const { get_full_path } = require('../utils/transform_tool');
6
7
  const { info } = require("../utils/log_tool");
7
8
  const { progress } = require("../utils/display_tool");
8
9
 
@@ -1,4 +1,5 @@
1
- const { get_fst_file, get_full_path } = require("../utils/file_tool");
1
+ const { get_fst_file } = require("../utils/file_tool");
2
+ const { get_full_path } = require('../utils/transform_tool');
2
3
  const { BaseCommand } = require("./base");
3
4
  const Jimp = require("jimp");
4
5