node-automator 1.4.31 → 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.
@@ -1,4 +1,5 @@
1
- const { get_file_list, get_full_path } = require("../utils/file_tool");
1
+ const { get_file_list } = require("../utils/file_tool");
2
+ const { get_full_path } = require('../utils/transform_tool');
2
3
  const { BaseCommand } = require("./base");
3
4
 
4
5
  class MergeFileCommand extends BaseCommand {
package/commands/mgr.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { progress } = require("../utils/display_tool");
2
- const { eval_code } = require("./share_data");
2
+ const { eval_code, getDataByKey } = require("./share_data");
3
3
  const { read_plain } = require("../utils/file_tool");
4
4
  const { pause } = require("../utils/interaction_tool");
5
5
  const {
@@ -139,10 +139,14 @@ const { EncryptCommand } = require('./encrypt');
139
139
  const { DecryptCommand } = require('./decrypt');
140
140
  const { TinifyCommand } = require('./tinify');
141
141
  const { WatchCommand } = require('./watch');
142
+ const { FilesGitCommand } = require('./files_git');
143
+ const { FontminCommand } = require('./fontmin');
144
+ const { BorderCommand } = require('./border');
142
145
 
143
146
  const globalData = {
144
147
  executed_cfg: [], // 执行过的配置文件
145
148
  process_state: PROCESS_STATE.NONE, // 当前执行状态
149
+ currentTitle: "未知", // 当前执行的配置文件
146
150
  };
147
151
 
148
152
  /**
@@ -277,6 +281,9 @@ async function _execSingle(commandCfg, depth, progressData) {
277
281
  formatData(commandCfg);
278
282
  setLastCommand(rawCfg, commandCfg);
279
283
  }
284
+ if (commandCfg.title) {
285
+ globalData.currentTitle = commandCfg.title;
286
+ }
280
287
  if (commandCfg.side_effect && shareData.DRY_RUN) {
281
288
  warn(`[DRY_RUN] ${commandCfg.title || commandCfg.type}`);
282
289
  return;
@@ -330,17 +337,9 @@ async function _execSingle(commandCfg, depth, progressData) {
330
337
  if (data.content != null) {
331
338
  content = data.content;
332
339
  } else if (data.key) {
333
- const keys = data.key.split(".");
334
- content = command.shareData;
335
- for (let i = 0; i < keys.length; i++) {
336
- const key = keys[i];
337
- content = content[key];
338
- if (!content) {
339
- break;
340
- }
341
- }
340
+ content = getDataByKey(data.key);
342
341
  } else if (data.src) {
343
- content = read_plain(data.src);
342
+ content = read_plain(data.src, command.getRequireContentOptions());
344
343
  } else if (data.code) {
345
344
  content = eval_code(data.code);
346
345
  }
@@ -529,10 +528,14 @@ function init() {
529
528
  register("decrypt", DecryptCommand, false);
530
529
  register("tinify", TinifyCommand, false);
531
530
  register("watch", WatchCommand, false);
531
+ register("files_git", FilesGitCommand, false);
532
+ register("fontmin", FontminCommand, false);
533
+ register("border", BorderCommand, false);
532
534
  }
533
535
 
534
536
  module.exports = {
535
537
  init,
536
538
  exec,
537
539
  execSingle,
540
+ globalData,
538
541
  };
@@ -13,9 +13,13 @@ class ReadOnlyExcelCommand extends BaseCommand {
13
13
  for (const sheetName in ret.Sheets) {
14
14
  const sheetData = ret.Sheets[sheetName];
15
15
  const sheetArray = [];
16
+ const ref = sheetData["!ref"];
17
+ if (!ref) {
18
+ continue;
19
+ }
16
20
  // "A1:Y71"
17
21
  /** @type {string[]} */
18
- const refs = sheetData["!ref"].split(":");
22
+ const refs = ref.split(":");
19
23
  const colBeg = sc.fromStr(/[A-Z]+/.exec(refs[0])[0]);
20
24
  const rowBeg = /[0-9]+/.exec(refs[0])[0];
21
25
  const colEnd = sc.fromStr(/[A-Z]+/.exec(refs[1])[0]);
@@ -1,6 +1,5 @@
1
1
  const path = require("node:path");
2
2
  const fs = require("node:fs");
3
- const os = require("node:os");
4
3
  const { argv } = require("yargs");
5
4
  const pinyin = require("pinyin").pinyin;
6
5
  const {
@@ -11,7 +10,8 @@ const {
11
10
  formatDate,
12
11
  formatTimeInMillisec,
13
12
  formatTimestampMillisec,
14
- format,
13
+ format_bytes,
14
+ get_full_path,
15
15
  } = require("../utils/transform_tool");
16
16
  const { parse_sync, stringify_sync } = require("../utils/parse_tool");
17
17
  const { parseDirective } = require("../utils/parse_directive");
@@ -157,36 +157,6 @@ function formatData(data) {
157
157
  return data;
158
158
  }
159
159
 
160
- /**
161
- * 获取完整真实路径
162
- * @param {*} src
163
- */
164
- function get_full_path(src, createIfNotExist) {
165
- const dstDir = path
166
- .resolve(src.replace(/^~/, os.homedir()))
167
- .replace(/\\/g, "/");
168
- let targetDir;
169
- switch (createIfNotExist) {
170
- case "FILE": {
171
- targetDir = path.dirname(dstDir);
172
- break;
173
- }
174
- case "FOLDER": {
175
- targetDir = dstDir;
176
- break;
177
- }
178
- }
179
- if (
180
- targetDir &&
181
- (!fs.existsSync(targetDir) || !fs.lstatSync(targetDir).isDirectory())
182
- ) {
183
- fs.mkdirSync(targetDir, {
184
- recursive: true,
185
- });
186
- }
187
- return dstDir;
188
- }
189
-
190
160
  function call_code(code) {
191
161
  return eval(`(${code})`);
192
162
  }
@@ -388,6 +358,10 @@ function processPipe(val, pipename, pipeargs) {
388
358
  val = val == null ? pipeargs[0] : val;
389
359
  break;
390
360
  }
361
+ case "or": {
362
+ val = val || pipeargs[0];
363
+ break;
364
+ }
391
365
  case "first": {
392
366
  val = val?.[0];
393
367
  break;
@@ -418,6 +392,10 @@ function processPipe(val, pipename, pipeargs) {
418
392
  val = formatDate(val, pipeargs?.[0]);
419
393
  break;
420
394
  }
395
+ case "format_bytes": {
396
+ val = format_bytes(val);
397
+ break;
398
+ }
421
399
  case "format_timestamp": {
422
400
  val = formatTimestampMillisec(val, pipeargs?.[0]);
423
401
  break;
@@ -567,7 +545,7 @@ function processPipe(val, pipename, pipeargs) {
567
545
  return fs.statSync(val).ctimeMs;
568
546
  }
569
547
  case "path": {
570
- val = get_full_path(val);
548
+ val = get_full_path(val, ...pipeargs);
571
549
  break;
572
550
  }
573
551
  case "basename": {
@@ -792,6 +770,5 @@ module.exports = {
792
770
  LINE_EXPRESSION,
793
771
  formatData,
794
772
  getDataByKey,
795
- get_full_path,
796
773
  eval_code,
797
774
  };
@@ -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 { getByteSize } = require("../utils/transform_tool");
3
4
  const { BaseCommand } = require("./base");
4
5
 
@@ -1,6 +1,7 @@
1
1
  const { BaseCommand } = require("./base");
2
2
  const fs = require("node:fs");
3
- const { get_fst_file, get_full_path } = require("../utils/file_tool");
3
+ const { get_fst_file } = require("../utils/file_tool");
4
+ const { get_full_path } = require('../utils/transform_tool');
4
5
  class StreamCommand extends BaseCommand {
5
6
  async execute() {
6
7
  if (this.selfData.type === "write") {
@@ -1,8 +1,7 @@
1
1
  const {
2
2
  get_file_list,
3
- get_full_path,
4
- format_bytes,
5
3
  } = require("../utils/file_tool");
4
+ const { get_full_path } = require('../utils/transform_tool');
6
5
  const { success, getPrint, info, warn, whisper } = require("../utils/log_tool");
7
6
  const { BaseCommand } = require("./base");
8
7
  const path = require("node:path");
@@ -10,6 +9,7 @@ const { progress } = require("../utils/display_tool");
10
9
  const { createWriteStream, readFileSync } = require("node:fs");
11
10
  const { getCache, setCache } = require("../utils/cache_tool");
12
11
  const crypto = require("node:crypto");
12
+ const { format_bytes } = require('../utils/transform_tool');
13
13
 
14
14
  class TencentCosCommand extends BaseCommand {
15
15
  async execute() {
@@ -245,12 +245,12 @@ class TencentCosCommand extends BaseCommand {
245
245
  srcs = srcs.filter((src) => {
246
246
  const cachedValue =
247
247
  cachedFiles[
248
- path
249
- .join(
250
- dst_folder,
251
- path.relative(base, src),
252
- )
253
- .replace(/\\/g, "/")
248
+ path
249
+ .join(
250
+ dst_folder,
251
+ path.relative(base, src),
252
+ )
253
+ .replace(/\\/g, "/")
254
254
  ];
255
255
  switch (strategy) {
256
256
  case "lazy": {
@@ -352,7 +352,7 @@ class TencentCosCommand extends BaseCommand {
352
352
  });
353
353
  cos.uploadFiles({
354
354
  files: files,
355
- onProgress: (_progressInfo) => {},
355
+ onProgress: (_progressInfo) => { },
356
356
  onFileFinish: (err, _data) => {
357
357
  if (err) {
358
358
  setCache(
@@ -1,9 +1,10 @@
1
1
  const { BaseCommand } = require("./base");
2
2
  const tinify = require("tinify");
3
3
  const Jimp = require("jimp");
4
- const { get_fst_file, get_full_path } = require('../utils/file_tool');
4
+ const { get_fst_file } = require('../utils/file_tool');
5
5
  const { dirname } = require('node:path');
6
6
  const fs = require('node:fs');
7
+ const { get_full_path, format_bytes } = require('../utils/transform_tool');
7
8
 
8
9
  class TinifyCommand extends BaseCommand {
9
10
  async execute() {
@@ -11,6 +12,9 @@ class TinifyCommand extends BaseCommand {
11
12
  tinify.key = data.api_key;
12
13
  const src = get_fst_file(data.src);
13
14
  const dstFile = get_full_path(data.dst, "FILE");
15
+ // 获取压缩前的文件大小
16
+ const srcStat = fs.statSync(src);
17
+ const srcSize = srcStat.size;
14
18
  fs.copyFileSync(src, dstFile);
15
19
  const dstDir = dirname(dstFile);
16
20
  if (!fs.existsSync(dstDir)) {
@@ -37,8 +41,25 @@ class TinifyCommand extends BaseCommand {
37
41
  }
38
42
  await tinifyData.toFile(dstFile);
39
43
  const compressionCount = tinify.compressionCount;
44
+ const dst = get_full_path(data.dst, "FILE");
45
+ const dstStat = fs.statSync(dst);
46
+ const dstSize = dstStat.size;
47
+ const sizeChanged = dstSize - srcSize;
48
+ const srcSizeFormated = format_bytes(srcSize);
49
+ const dstSizeFormated = format_bytes(dstSize);
50
+ const sizeRatioChanged = (1 - dstSize / srcSize);
51
+ const sizeRatioChangedFormated = `${(sizeRatioChanged * 100 * -1).toFixed(2)}%`;
52
+ const sizeChangedFormated = format_bytes(sizeChanged);
40
53
  return {
41
54
  compressionCount,
55
+ srcSize,
56
+ srcSizeFormated,
57
+ dstSize,
58
+ dstSizeFormated,
59
+ sizeRatioChanged,
60
+ sizeRatioChangedFormated,
61
+ sizeChanged,
62
+ sizeChangedFormated,
42
63
  };
43
64
  }
44
65
 
package/commands/unzip.js CHANGED
@@ -1,14 +1,13 @@
1
1
  const {
2
2
  read_plain,
3
3
  get_fst_file,
4
- get_full_path,
5
- format_bytes,
6
4
  } = require("../utils/file_tool");
7
5
  const { BaseCommand } = require("./base");
8
6
  const fs = require("node:fs");
9
7
  const il = require("iconv-lite");
10
8
  const path = require("node:path");
11
9
  const display_tool = require("../utils/display_tool");
10
+ const { format_bytes, get_full_path } = require('../utils/transform_tool');
12
11
 
13
12
  class UnzipCommand extends BaseCommand {
14
13
  async execute() {
package/commands/watch.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const { watch } = require('node:fs');
2
2
  const { BaseCommand } = require("./base");
3
- const { get_full_path } = require('./share_data');
3
+ const { get_full_path } = require('../utils/transform_tool');
4
4
  const { throttle } = require('../utils/func_tool');
5
5
 
6
6
  class WatchCommand extends BaseCommand {
@@ -1,11 +1,13 @@
1
- const { write_with_type } = require("../utils/file_tool");
1
+ const { write_with_type, get_fst_file } = require("../utils/file_tool");
2
+ const { get_full_path } = require('../utils/transform_tool');
2
3
  const { BaseCommand } = require("./base");
3
4
 
4
5
  class WriteCfgCommand extends BaseCommand {
5
6
  async execute() {
6
7
  const data = this.selfData;
7
8
  const content = this.content;
8
- await write_with_type(content, data.dst, data.type, data.options);
9
+ const dst = get_fst_file(data.dst) || get_full_path(data.dst);
10
+ await write_with_type(content, dst, data.type, data.options);
9
11
  }
10
12
 
11
13
  getRequireContent() {
@@ -14,6 +14,7 @@ class WriteClipboard extends BaseCommand {
14
14
  } catch (_error) {
15
15
  warn("写入剪切板失败");
16
16
  }
17
+ return content;
17
18
  }
18
19
 
19
20
  getRequireContent() {
@@ -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
 
4
5
  class WriteExcelCommand extends BaseCommand {
package/commands/zip.js CHANGED
@@ -3,9 +3,9 @@ const path = require("node:path");
3
3
  const {
4
4
  get_file_list,
5
5
  get_fst_file,
6
- get_full_path,
7
6
  processRename,
8
7
  } = require("../utils/file_tool");
8
+ const { get_full_path } = require('../utils/transform_tool');
9
9
  const fs = require("node:fs");
10
10
  const { progress } = require("../utils/display_tool");
11
11
  const { Alignment, alignStr } = require("../utils/transform_tool");
@@ -1,11 +1,9 @@
1
1
  const { BaseCommand } = require("./base");
2
2
  const _path = require("node:path");
3
3
  const {
4
- get_file_list,
5
- get_fst_file,
6
- get_full_path,
7
4
  processRename,
8
5
  } = require("../utils/file_tool");
6
+ const { get_full_path } = require('../utils/transform_tool');
9
7
  const _fs = require("node:fs");
10
8
  const { progress } = require("../utils/display_tool");
11
9
  const { Alignment, alignStr } = require("../utils/transform_tool");
@@ -1,6 +1,7 @@
1
1
  const { BaseCommand } = require("./base");
2
2
  const path = require("node:path");
3
- const { get_fst_file, get_full_path } = require("../utils/file_tool");
3
+ const { get_fst_file } = require("../utils/file_tool");
4
+ const { get_full_path } = require('../utils/transform_tool');
4
5
  const { info } = require("../utils/log_tool");
5
6
  const { progress } = require("../utils/display_tool");
6
7
  const { eval_code } = require("./share_data");
@@ -13,16 +14,16 @@ class ZipFolderCommand extends BaseCommand {
13
14
  const isAccept = data.filter
14
15
  ? eval_code(data.filter)
15
16
  : (filename) => {
16
- // 排除以 . 开头的文件、目录
17
- if (
18
- filename
19
- .split(path.sep)
20
- .some((part) => part.startsWith("."))
21
- ) {
22
- return false;
23
- }
24
- return true;
25
- };
17
+ // 排除以 . 开头的文件、目录
18
+ if (
19
+ filename
20
+ .split(path.sep)
21
+ .some((part) => part.startsWith("."))
22
+ ) {
23
+ return false;
24
+ }
25
+ return true;
26
+ };
26
27
  // creating archives
27
28
  var zip = new AdmZip();
28
29
  let i = 0;
package/index.js CHANGED
@@ -9,10 +9,10 @@ require("dotenv").config({
9
9
 
10
10
  // 禁用其他打印
11
11
  console.yzplog = console.log;
12
- console.log = () => {};
13
- console.warn = () => {};
14
- console.info = () => {};
15
- console.error = () => {};
12
+ console.log = () => { };
13
+ console.warn = () => { };
14
+ console.info = () => { };
15
+ console.error = () => { };
16
16
 
17
17
  const mgr = require("./commands/mgr");
18
18
  const {
@@ -66,7 +66,7 @@ async function MainProcess() {
66
66
  let lastError = null;
67
67
  const cfgs = await getCfgs();
68
68
  // 缓存Key考虑cfgs, argv
69
- const hashSource = [cfgs, JSON.stringify(argv)].join("&&&");
69
+ const hashSource = [cfgs, JSON.stringify(argv), process.cwd()].join("&&&");
70
70
  const cfgHash = hash(hashSource).toString();
71
71
  const enableTimer = argv.ENABLE_TIMER;
72
72
  const timerInterval = argv.TIMER_INTERVAL || 1000;
@@ -85,14 +85,14 @@ async function MainProcess() {
85
85
  )}] 任务已运行 ${formatTimeInMillisec(
86
86
  elapsedTime,
87
87
  "hh:mm:ss",
88
- )} ${clear_line_end}`,
88
+ )} (当前:${mgr.globalData.currentTitle}) ${clear_line_end}`,
89
89
  "",
90
90
  );
91
91
  } else {
92
92
  const _estimateLeft = Math.max(0, estimateTime - elapsedTime);
93
93
  // info(`\r[${formatTimestampMillisec(Date.now())}] 任务已运行 ${formatTimeInMillisec(elapsedTime, "hh:mm:ss")}, 预计剩余 ${formatTimeInMillisec(estimateLeft, "hh:mm:ss")} ${clear_line_end}`, "");
94
94
  progress(elapsedTime, estimateTime, {
95
- desc: "预计耗时",
95
+ desc: `预计耗时 (当前:${mgr.globalData.currentTitle})`,
96
96
  format_time: true,
97
97
  color: _estimateLeft > 0 ? "reset" : "red",
98
98
  });
@@ -154,13 +154,13 @@ async function MainProcess() {
154
154
  var [lastCommand, lastCommandFormated] = getLastCommand();
155
155
  error2(
156
156
  "File:\n" +
157
- getLastExecFile() +
158
- "\nRaw:\n" +
159
- lastCommand +
160
- "\nFormated:\n" +
161
- JSON.stringify(lastCommandFormated, null, 4) +
162
- "\n" +
163
- lastError,
157
+ getLastExecFile() +
158
+ "\nRaw:\n" +
159
+ lastCommand +
160
+ "\nFormated:\n" +
161
+ JSON.stringify(lastCommandFormated, null, 4) +
162
+ "\n" +
163
+ lastError,
164
164
  );
165
165
  }
166
166
  if (shareData.ALERT_RESULT) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-automator",
3
- "version": "1.4.31",
3
+ "version": "1.4.32",
4
4
  "description": "Execute automation with yaml configuration(compatible with json)",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -34,10 +34,12 @@
34
34
  "dotenv": "^16.1.4",
35
35
  "emailjs": "^4.0.2",
36
36
  "exceljs": "^4.3.0",
37
+ "fontmin": "^1.1.1",
37
38
  "ftp": "^0.3.10",
38
39
  "glob": "^7.1.6",
39
40
  "html-to-text": "^8.2.0",
40
41
  "iconv-lite": "^0.6.2",
42
+ "ignore-walk": "^8.0.0",
41
43
  "image-size": "^1.0.0",
42
44
  "imagemin": "^9.0.1",
43
45
  "imagemin-mozjpeg": "^10.0.0",
@@ -1,6 +1,7 @@
1
1
  const glob = require("glob");
2
2
  const path = require("node:path");
3
3
  const fs = require("node:fs");
4
+ const os = require("node:os");
4
5
  const display_tool = require("./display_tool");
5
6
  const { info, whisper, warn, setLastError } = require("../utils/log_tool");
6
7
  const tmps = {
@@ -9,10 +10,12 @@ const tmps = {
9
10
  const iconv = require("iconv-lite");
10
11
  // const chardet = require("jschardet");
11
12
  const chardet = require("chardet");
12
- const { get_full_path } = require("../commands/share_data");
13
13
  const { parse, stringify } = require("./parse_tool");
14
14
  const { hash } = require("./hash_tool");
15
- const { formatDate } = require("./transform_tool");
15
+ const { formatDate, format_bytes, get_full_path } = require("./transform_tool");
16
+
17
+
18
+
16
19
  /**
17
20
  *
18
21
  * @param {Array | string} src
@@ -58,38 +61,16 @@ function get_file_size(src) {
58
61
  function get_files_size(files) {
59
62
  return files.reduce((prev, file) => {
60
63
  const stat = fs.statSync(file);
64
+ if (stat.isDirectory()) {
65
+ // 获取所有子文件
66
+ const childFiles = get_file_list(file + "\\**\\*", undefined, true);
67
+ // 递归计算子文件大小
68
+ prev += get_files_size(childFiles);
69
+ }
61
70
  return (stat.size += prev);
62
71
  }, 0);
63
72
  }
64
73
 
65
- function format_bytes(size) {
66
- if (size < 2 ** 10) {
67
- return `${size}B`;
68
- }
69
- if (size < 2 ** 20) {
70
- return `${+(size / 2 ** 10).toFixed(2)}KB`;
71
- }
72
- if (size < 2 ** 30) {
73
- return `${+(size / 2 ** 20).toFixed(2)}MB`;
74
- }
75
- if (size < 2 ** 40) {
76
- return `${+(size / 2 ** 30).toFixed(2)}GB`;
77
- }
78
- if (size < 2 ** 50) {
79
- return `${+(size / 2 ** 40).toFixed(2)}TB`;
80
- }
81
- if (size < 2 ** 60) {
82
- return `${+(size / 2 ** 50).toFixed(2)}PB`;
83
- }
84
- if (size < 2 ** 70) {
85
- return `${+(size / 2 ** 60).toFixed(2)}EB`;
86
- }
87
- if (size < 2 ** 80) {
88
- return `${+(size / 2 ** 70).toFixed(2)}ZB`;
89
- }
90
- return `${+(size / 2 ** 80).toFixed(2)}YB`;
91
- }
92
-
93
74
  function read_plain(src, options) {
94
75
  try {
95
76
  const fst_src = get_fst_file(src);
@@ -577,7 +558,6 @@ async function remove(src, options) {
577
558
  module.exports = {
578
559
  get_file_list,
579
560
  get_fst_file,
580
- get_full_path,
581
561
  read_cfg,
582
562
  read_plain,
583
563
  write_plain,
@@ -590,6 +570,5 @@ module.exports = {
590
570
  move_local,
591
571
  get_file_size,
592
572
  get_files_size,
593
- format_bytes,
594
573
  processRename,
595
574
  };
@@ -1,4 +1,4 @@
1
- const { get_full_path, remove } = require("./file_tool");
1
+ const { get_full_path } = require('../utils/transform_tool');
2
2
  const transform_tool = require("./transform_tool");
3
3
  const { warn, info, log, setLastError } = require("./log_tool");
4
4
  const { progress } = require("./display_tool");
package/utils/scm_tool.js CHANGED
@@ -3,7 +3,8 @@ const { vital, log, warn } = require("./log_tool");
3
3
  const { exec_shell } = require("./shell_tool");
4
4
  const path = require("node:path");
5
5
  const fs = require("node:fs");
6
- const { copy, get_fst_file, get_full_path } = require("./file_tool");
6
+ const { copy, get_fst_file } = require("./file_tool");
7
+ const { get_full_path } = require('../utils/transform_tool');
7
8
 
8
9
  async function svn_is_dirty(cwd) {
9
10
  return await exec_shell(