w-md2docx 1.0.0

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.
Files changed (69) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +55 -0
  4. package/.jsdoc +25 -0
  5. package/LICENSE +21 -0
  6. package/README.md +83 -0
  7. package/SECURITY.md +5 -0
  8. package/babel.config.js +16 -0
  9. package/dist/w-md2docx.umd.js +7 -0
  10. package/dist/w-md2docx.umd.js.map +1 -0
  11. package/docs/ApiClient.mjs.html +539 -0
  12. package/docs/ApiServer.mjs.html +516 -0
  13. package/docs/WMd2docx.mjs.html +101 -0
  14. package/docs/cvMdTo.mjs.html +288 -0
  15. package/docs/cvMdToDocx.mjs.html +243 -0
  16. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  17. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  18. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  19. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  20. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  21. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  23. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  24. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  25. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  26. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  27. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  28. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  34. package/docs/global.html +5453 -0
  35. package/docs/index.html +84 -0
  36. package/docs/scripts/collapse.js +39 -0
  37. package/docs/scripts/commonNav.js +28 -0
  38. package/docs/scripts/linenumber.js +25 -0
  39. package/docs/scripts/nav.js +12 -0
  40. package/docs/scripts/polyfill.js +4 -0
  41. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  42. package/docs/scripts/prettify/lang-css.js +2 -0
  43. package/docs/scripts/prettify/prettify.js +28 -0
  44. package/docs/scripts/search.js +99 -0
  45. package/docs/styles/jsdoc.css +776 -0
  46. package/docs/styles/prettify.css +80 -0
  47. package/docs/utils.mjs.html +520 -0
  48. package/g.mjs +34 -0
  49. package/package.json +32 -0
  50. package/script.txt +18 -0
  51. package/src/ApiClient.mjs +467 -0
  52. package/src/ApiServer.mjs +444 -0
  53. package/src/WMd2docx.mjs +29 -0
  54. package/src/cvMdTo.mjs +216 -0
  55. package/src/cvMdToDocx.mjs +171 -0
  56. package/src/templates/temp_tpc.docx +0 -0
  57. package/src/utils.mjs +448 -0
  58. package/test/api-ApiClient.test.mjs +221 -0
  59. package/test/api-ApiServer.test.mjs +191 -0
  60. package/test/cocktail.svg +3 -0
  61. package/test/report.md +257 -0
  62. package/test/unit-cvMdTo.test.mjs +132 -0
  63. package/test/unit-cvMdToDocx.test.mjs +185 -0
  64. package/test/unit-utils.test.mjs +370 -0
  65. package/toolg/addVersion.mjs +4 -0
  66. package/toolg/cleanFolder.mjs +5 -0
  67. package/toolg/gDistRollup.mjs +56 -0
  68. package/toolg/gDocsExams.mjs +51 -0
  69. package/toolg/modifyReadme.mjs +4 -0
@@ -0,0 +1,80 @@
1
+ .pln {
2
+ color: #ddd;
3
+ }
4
+
5
+ /* string content */
6
+ .str {
7
+ color: #61ce3c;
8
+ }
9
+
10
+ /* a keyword */
11
+ .kwd {
12
+ color: #fbde2d;
13
+ }
14
+
15
+ /* a comment */
16
+ .com {
17
+ color: #aeaeae;
18
+ }
19
+
20
+ /* a type name */
21
+ .typ {
22
+ color: #8da6ce;
23
+ }
24
+
25
+ /* a literal value */
26
+ .lit {
27
+ color: #fbde2d;
28
+ }
29
+
30
+ /* punctuation */
31
+ .pun {
32
+ color: #ddd;
33
+ }
34
+
35
+ /* lisp open bracket */
36
+ .opn {
37
+ color: #000000;
38
+ }
39
+
40
+ /* lisp close bracket */
41
+ .clo {
42
+ color: #000000;
43
+ }
44
+
45
+ /* a markup tag name */
46
+ .tag {
47
+ color: #8da6ce;
48
+ }
49
+
50
+ /* a markup attribute name */
51
+ .atn {
52
+ color: #fbde2d;
53
+ }
54
+
55
+ /* a markup attribute value */
56
+ .atv {
57
+ color: #ddd;
58
+ }
59
+
60
+ /* a declaration */
61
+ .dec {
62
+ color: #EF5050;
63
+ }
64
+
65
+ /* a variable name */
66
+ .var {
67
+ color: #c82829;
68
+ }
69
+
70
+ /* a function name */
71
+ .fun {
72
+ color: #4271ae;
73
+ }
74
+
75
+ /* Specify class=linenums on a pre to get line numbering */
76
+ ol.linenums {
77
+ margin-top: 0;
78
+ margin-bottom: 0;
79
+ padding-bottom: 2px;
80
+ }
@@ -0,0 +1,520 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>utils.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#ApiServer">ApiServer</a></li><li><a href="global.html#WMd2docx">WMd2docx</a></li><li><a href="global.html#checkDocxReady">checkDocxReady</a></li><li><a href="global.html#cleanWorkDir">cleanWorkDir</a></li><li><a href="global.html#cvMdTo">cvMdTo</a></li><li><a href="global.html#cvMdToDocx">cvMdToDocx</a></li><li><a href="global.html#getFpExe">getFpExe</a></li><li><a href="global.html#getQueueSize">getQueueSize</a></li><li><a href="global.html#health">health</a></li><li><a href="global.html#readAssets">readAssets</a></li><li><a href="global.html#resolveAssetPaths">resolveAssetPaths</a></li><li><a href="global.html#retryBusy">retryBusy</a></li><li><a href="global.html#runExclusive">runExclusive</a></li><li><a href="global.html#scanAssetPaths">scanAssetPaths</a></li><li><a href="global.html#toErrText">toErrText</a></li><li><a href="global.html#toSafeName">toSafeName</a></li><li><a href="global.html#transErrAsset">transErrAsset</a></li><li><a href="global.html#writeAssets">writeAssets</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">utils.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import fs from 'fs'
49
+ import path from 'path'
50
+ import get from 'lodash-es/get.js'
51
+ import isstr from 'wsemi/src/isstr.mjs'
52
+ import isestr from 'wsemi/src/isestr.mjs'
53
+ import isarr from 'wsemi/src/isarr.mjs'
54
+ import isfun from 'wsemi/src/isfun.mjs'
55
+ import ispint from 'wsemi/src/ispint.mjs'
56
+ import ispnum from 'wsemi/src/ispnum.mjs'
57
+ import cint from 'wsemi/src/cint.mjs'
58
+ import cdbl from 'wsemi/src/cdbl.mjs'
59
+ import fsIsFile from 'wsemi/src/fsIsFile.mjs'
60
+ import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
61
+
62
+
63
+ //mime
64
+ let mimeHtml = 'text/html; charset=utf-8'
65
+ let mimeDocx = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
66
+
67
+
68
+ /**
69
+ * 取錯誤訊息文字
70
+ *
71
+ * 底層轉檔器之reject可能為字串或Error物件,統一轉為文字供組合訊息使用。
72
+ *
73
+ * @param {*} err 輸入錯誤資料,可為字串、Error物件或任意資料
74
+ * @returns {String} 回傳錯誤訊息字串
75
+ * @example
76
+ *
77
+ * console.log(toErrText('abc'))
78
+ * // => 'abc'
79
+ *
80
+ * console.log(toErrText(new Error('abc')))
81
+ * // => 'abc'
82
+ *
83
+ */
84
+ function toErrText(err) {
85
+ if (isstr(err)) {
86
+ return err
87
+ }
88
+ if (isestr(get(err, 'message'))) {
89
+ return err.message
90
+ }
91
+ let s = ''
92
+ try {
93
+ s = JSON.stringify(err) //undefined/function/symbol 會回 undefined 而非字串
94
+ }
95
+ catch (e) {
96
+ s = ''
97
+ }
98
+ if (!isstr(s)) {
99
+ s = String(err)
100
+ }
101
+ return s
102
+ }
103
+
104
+
105
+ /**
106
+ * 重試短暫被鎖定之檔案操作
107
+ *
108
+ * Windows下新建檔案常被防毒或索引器即時掃描而短暫鎖定(EBUSY/EPERM),遇此類錯誤時延遲後重試以提高穩定性,其餘錯誤直接拋出。
109
+ *
110
+ * @param {Function} fun 輸入欲執行之函數,可回傳Promise
111
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
112
+ * @param {Integer} [opt.tries=6] 輸入最多嘗試次數整數,預設6
113
+ * @param {Number} [opt.delayMs=400] 輸入每次重試前延遲毫秒數,預設400
114
+ * @returns {Promise} 回傳Promise,resolve回傳fun之結果,reject回傳錯誤
115
+ * @example
116
+ *
117
+ * let r = await retryBusy(() => fs.promises.readFile('./a.txt', 'utf8'), { tries: 3 })
118
+ *
119
+ */
120
+ async function retryBusy(fun, opt = {}) {
121
+
122
+ //check
123
+ if (!isfun(fun)) {
124
+ return Promise.reject('fun must be a function')
125
+ }
126
+
127
+ //tries
128
+ let tries = get(opt, 'tries', null)
129
+ if (!ispint(tries)) {
130
+ tries = 6
131
+ }
132
+ tries = cint(tries)
133
+
134
+ //delayMs
135
+ let delayMs = get(opt, 'delayMs', null)
136
+ if (!ispnum(delayMs)) {
137
+ delayMs = 400
138
+ }
139
+ delayMs = cdbl(delayMs)
140
+
141
+ let errLast = null
142
+ for (let i = 0; i &lt; tries; i++) {
143
+ try {
144
+ return await fun()
145
+ }
146
+ catch (err) {
147
+ errLast = err
148
+ let code = get(err, 'code', '')
149
+ let busy = (code === 'EBUSY' || code === 'EPERM')
150
+ if (busy &amp;&amp; i &lt; tries - 1) {
151
+ await new Promise((resolve) => setTimeout(resolve, delayMs))
152
+ continue
153
+ }
154
+ throw err
155
+ }
156
+ }
157
+ throw errLast
158
+ }
159
+
160
+
161
+ //pmChain: docx 轉檔須逐一序列化
162
+ //why: 底層係調用本機 Word(win32com)進行轉檔, 同時執行多份會相互干擾(共用 Word 實例、模板檔遭鎖定),
163
+ // 故不論同時進來幾個請求, docx 階段一律排隊逐一執行; md 轉 html 為純 JS 不受此限
164
+ let pmChain = Promise.resolve()
165
+ let nQueue = 0
166
+
167
+
168
+ /**
169
+ * 將函數排入Word轉檔佇列並逐一執行
170
+ *
171
+ * 底層係調用本機Word(win32com)轉檔,同時執行多份會相互干擾,故docx階段一律排隊逐一執行;前一件不論成敗均續行下一件。
172
+ *
173
+ * @param {Function} fun 輸入欲執行之函數,可回傳Promise
174
+ * @returns {Promise} 回傳Promise,resolve回傳fun之結果,reject回傳錯誤
175
+ * @example
176
+ *
177
+ * let r = await runExclusive(() => WHtml2docx('./a.html', './a.docx'))
178
+ *
179
+ */
180
+ function runExclusive(fun) {
181
+
182
+ //check
183
+ if (!isfun(fun)) {
184
+ return Promise.reject('fun must be a function')
185
+ }
186
+
187
+ nQueue++
188
+ let pm = pmChain.then(fun, fun) //前一件不論成敗均續行
189
+ pmChain = pm.then(() => {
190
+ nQueue--
191
+ }, () => {
192
+ nQueue--
193
+ })
194
+ return pm
195
+ }
196
+
197
+
198
+ /**
199
+ * 取當前排隊中(含執行中)之docx轉檔數
200
+ *
201
+ * @returns {Integer} 回傳排隊數整數
202
+ * @example
203
+ *
204
+ * console.log(getQueueSize())
205
+ * // => 0
206
+ *
207
+ */
208
+ function getQueueSize() {
209
+ return nQueue
210
+ }
211
+
212
+
213
+ /**
214
+ * 取w-html2docx之htmlToDocx.exe位置
215
+ *
216
+ * 注意:w-html2docx內部以「模組載入當下之工作目錄(cwd)」推導exe位置,故此處以同一規則檢查,使事前檢查與實際轉檔行為一致;查無即代表由此cwd執行必定轉檔失敗。
217
+ *
218
+ * @returns {String} 回傳exe位置字串,查無則回傳空字串
219
+ * @example
220
+ *
221
+ * console.log(getFpExe())
222
+ * // => 'C:\\proj\\node_modules\\w-html2docx\\src\\htmlToDocx.exe'
223
+ *
224
+ */
225
+ function getFpExe() {
226
+ let fdSrv = path.resolve() //= process.cwd(), 與 w-html2docx 內部同規則
227
+ let fps = [
228
+ path.resolve(fdSrv, 'src/htmlToDocx.exe'),
229
+ path.resolve(fdSrv, 'node_modules/w-html2docx/src/htmlToDocx.exe'),
230
+ ]
231
+ for (let fp of fps) {
232
+ if (fsIsFile(fp)) {
233
+ return fp
234
+ }
235
+ }
236
+ return ''
237
+ }
238
+
239
+
240
+ /**
241
+ * 檢查本機是否具備docx轉檔條件(Windows且轉檔器存在)
242
+ *
243
+ * 注意:僅代表環境條件成立,不代表Word確實可被調用;後者須實際轉一份小檔驗證。
244
+ *
245
+ * @returns {Object} 回傳狀態物件{platform,isWindows,cwd,exePath,exeFound,ready}
246
+ * @example
247
+ *
248
+ * console.log(checkDocxReady())
249
+ * // => { platform: 'win32', isWindows: true, cwd: '...', exePath: '...', exeFound: true, ready: true }
250
+ *
251
+ */
252
+ function checkDocxReady() {
253
+ let fpExe = getFpExe()
254
+ return {
255
+ platform: process.platform,
256
+ isWindows: process.platform === 'win32',
257
+ cwd: path.resolve(),
258
+ exePath: fpExe,
259
+ exeFound: fpExe !== '',
260
+ ready: process.platform === 'win32' &amp;&amp; fpExe !== '',
261
+ }
262
+ }
263
+
264
+
265
+ /**
266
+ * 取安全檔名主體
267
+ *
268
+ * 移除路徑成分、常見副檔名與跨平台非法字元,避免路徑穿越與寫檔失敗;無效輸入回傳'output'。
269
+ *
270
+ * @param {String} name 輸入檔名字串
271
+ * @returns {String} 回傳安全檔名主體字串
272
+ * @example
273
+ *
274
+ * console.log(toSafeName('../report/報告R00.01.md'))
275
+ * // => '報告R00.01'
276
+ *
277
+ * console.log(toSafeName(''))
278
+ * // => 'output'
279
+ *
280
+ */
281
+ function toSafeName(name) {
282
+ let s = ''
283
+ if (isstr(name)) {
284
+ s = name.trim()
285
+ }
286
+ if (s === '') {
287
+ return 'output'
288
+ }
289
+ s = path.basename(s) //去除任何路徑成分
290
+ s = s.replace(/\.(md|markdown|html|htm|docx)$/i, '') //去除常見副檔名
291
+ s = s.replace(/[\\/:*?"&lt;>|\r\n\t]/g, '_') //Windows 非法字元
292
+ s = s.replace(/^\.+/, '') //去除開頭點號(避免隱藏檔與相對路徑)
293
+ s = s.trim()
294
+ if (s === '') {
295
+ return 'output'
296
+ }
297
+ return s.slice(0, 120)
298
+ }
299
+
300
+
301
+ //checkAssetPath: 單一相對路徑之落點檢查
302
+ //why: 路徑可能來自外部輸入, 須以絕對路徑比對且尾端補分隔字元, 避免同前綴之兄弟資料夾被放行
303
+ function checkAssetPath(dirJob, rp, pOri) {
304
+
305
+ //拒絕絕對路徑(含磁碟機代號與 UNC)
306
+ if (/^[a-zA-Z]:/.test(rp) || rp.startsWith('/') || rp.startsWith('//')) {
307
+ throw new Error(`asset.path must be a relative path: ${pOri}`)
308
+ }
309
+
310
+ //落點檢查
311
+ let fp = path.resolve(dirJob, rp)
312
+ let base = path.resolve(dirJob) + path.sep
313
+ if (!fp.startsWith(base)) {
314
+ throw new Error(`asset.path is outside the working folder: ${pOri}`)
315
+ }
316
+
317
+ return fp
318
+ }
319
+
320
+
321
+ /**
322
+ * 解析資產之相對路徑並防護路徑穿越
323
+ *
324
+ * md內之圖片路徑可能為URL編碼(中文或空白),而轉檔器係以md內之原字串組路徑取圖;故原字串與解碼後字串不同時回傳兩者,供呼叫端寫入同一內容確保圖片必能被解析。
325
+ *
326
+ * @param {String} dirJob 輸入工作資料夾位置字串
327
+ * @param {String} p 輸入資產相對路徑字串
328
+ * @returns {Array} 回傳實際寫入之絕對路徑陣列
329
+ * @example
330
+ *
331
+ * console.log(resolveAssetPaths('C:\\job', 'pics/%E5%9C%96.png'))
332
+ * // => [ 'C:\\job\\pics\\%E5%9C%96.png', 'C:\\job\\pics\\圖.png' ]
333
+ *
334
+ */
335
+ function resolveAssetPaths(dirJob, p) {
336
+
337
+ //raw
338
+ let raw = ''
339
+ if (isstr(p)) {
340
+ raw = p.trim().replace(/\\/g, '/')
341
+ }
342
+ if (raw === '') {
343
+ throw new Error('asset.path is empty')
344
+ }
345
+
346
+ //解碼; 非法 percent 序列則視為未編碼
347
+ let dec = raw
348
+ try {
349
+ dec = decodeURIComponent(raw)
350
+ }
351
+ catch (err) {
352
+ dec = raw
353
+ }
354
+
355
+ let rps = (raw === dec) ? [raw] : [raw, dec]
356
+ return rps.map((rp) => checkAssetPath(dirJob, rp, p))
357
+ }
358
+
359
+
360
+ /**
361
+ * 將夾帶之資產(圖片等)還原至工作資料夾
362
+ *
363
+ * 使md內之相對路徑得以解析;base64容許data URI形式。
364
+ *
365
+ * @param {String} dirJob 輸入工作資料夾位置字串
366
+ * @param {Array} assets 輸入資產陣列,格式[{path,base64}],亦接受contentBase64欄位
367
+ * @returns {Integer} 回傳寫入之資產數整數
368
+ * @example
369
+ *
370
+ * let n = writeAssets('C:\\job', [{ path: 'pics/圖.png', base64: '…' }])
371
+ * // => 1
372
+ *
373
+ */
374
+ function writeAssets(dirJob, assets) {
375
+ let n = 0
376
+ if (!isarr(assets)) {
377
+ return n
378
+ }
379
+ for (let v of assets) {
380
+ let p = get(v, 'path', '')
381
+ let fps = resolveAssetPaths(dirJob, p)
382
+ let b64 = get(v, 'base64', '')
383
+ if (!isestr(b64)) {
384
+ b64 = get(v, 'contentBase64', '')
385
+ }
386
+ if (!isestr(b64)) {
387
+ throw new Error(`asset.base64 is empty: ${p}`)
388
+ }
389
+ b64 = b64.replace(/^data:[^;]+;base64,/, '') //容許 data URI 形式
390
+ let buf = Buffer.from(b64, 'base64')
391
+ for (let fp of fps) {
392
+ fs.mkdirSync(path.dirname(fp), { recursive: true })
393
+ fs.writeFileSync(fp, buf)
394
+ }
395
+ n++
396
+ }
397
+ return n
398
+ }
399
+
400
+
401
+ /**
402
+ * 清除工作資料夾內逾時未刪之作業夾(前綴job_)
403
+ *
404
+ * 工作資料夾不存在時自動建立。
405
+ *
406
+ * @param {String} dirWork 輸入工作資料夾位置字串
407
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
408
+ * @param {Number} [opt.msAge=21600000] 輸入視為逾時之毫秒數,預設6小時(21600000)
409
+ * @returns {Integer} 回傳刪除之作業夾數整數
410
+ * @example
411
+ *
412
+ * let n = cleanWorkDir('./tmp', { msAge: 3600 * 1000 })
413
+ * // => 2
414
+ *
415
+ */
416
+ function cleanWorkDir(dirWork, opt = {}) {
417
+
418
+ //check
419
+ if (!isestr(dirWork)) {
420
+ return 0
421
+ }
422
+
423
+ //msAge
424
+ let msAge = get(opt, 'msAge', null)
425
+ if (!ispnum(msAge)) {
426
+ msAge = 6 * 3600 * 1000
427
+ }
428
+ msAge = cdbl(msAge)
429
+
430
+ if (!fsIsFolder(dirWork)) {
431
+ fs.mkdirSync(dirWork, { recursive: true })
432
+ return 0
433
+ }
434
+ let n = 0
435
+ let msNow = Date.now()
436
+ for (let fn of fs.readdirSync(dirWork)) {
437
+ if (!fn.startsWith('job_')) {
438
+ continue
439
+ }
440
+ let fp = path.resolve(dirWork, fn)
441
+ try {
442
+ let st = fs.statSync(fp)
443
+ if (msNow - st.mtimeMs > msAge) {
444
+ fs.rmSync(fp, { recursive: true, force: true })
445
+ n++
446
+ }
447
+ }
448
+ catch (err) {
449
+ //忽略無法處理者
450
+ }
451
+ }
452
+ return n
453
+ }
454
+
455
+
456
+ /**
457
+ * 轉譯資產缺漏之錯誤訊息
458
+ *
459
+ * 底層僅回原始路徑訊息(且含內部工作夾路徑),此處改為可行動之提示並隱去工作路徑;非資產缺漏訊息原樣回傳。
460
+ *
461
+ * @param {String} msg 輸入原始錯誤訊息字串
462
+ * @returns {String} 回傳轉譯後錯誤訊息字串
463
+ * @example
464
+ *
465
+ * console.log(transErrAsset('fp[C:/tmp/job_1/pics/a.png] does not exist'))
466
+ * // => 'Failed to convert md to html: the referenced file (pics/a.png) does not exist, please make sure it is included in assets'
467
+ *
468
+ */
469
+ function transErrAsset(msg) {
470
+ if (!isstr(msg)) {
471
+ return toErrText(msg)
472
+ }
473
+ let mt = msg.match(/job_[^\\/]+[\\/](.+?)\]/)
474
+ if (/does not exist/i.test(msg) &amp;&amp; mt) {
475
+ return `Failed to convert md to html: the referenced file (${mt[1].replace(/\\/g, '/')}) does not exist, please make sure it is included in assets`
476
+ }
477
+ return msg
478
+ }
479
+
480
+
481
+ export {
482
+ mimeHtml,
483
+ mimeDocx,
484
+ toErrText,
485
+ retryBusy,
486
+ runExclusive,
487
+ getQueueSize,
488
+ getFpExe,
489
+ checkDocxReady,
490
+ toSafeName,
491
+ resolveAssetPaths,
492
+ writeAssets,
493
+ cleanWorkDir,
494
+ transErrAsset
495
+ }
496
+ </code></pre>
497
+ </article>
498
+ </section>
499
+
500
+
501
+
502
+
503
+
504
+
505
+ </div>
506
+
507
+ <br class="clear">
508
+
509
+ <footer>
510
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 01 2026 16:40:03 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
511
+ </footer>
512
+
513
+ <script>prettyPrint();</script>
514
+ <script src="scripts/polyfill.js"></script>
515
+ <script src="scripts/linenumber.js"></script>
516
+
517
+
518
+
519
+ </body>
520
+ </html>
package/g.mjs ADDED
@@ -0,0 +1,34 @@
1
+ import w from 'wsemi'
2
+ import WMd2docx from './src/WMd2docx.mjs'
3
+ //import WMd2docx from 'w-md2docx/src/WMd2docx.mjs'
4
+ //import WMd2docx from 'w-md2docx'
5
+
6
+
7
+ async function test() {
8
+
9
+ let fpIn = `./test/report.md`
10
+ let fpOut = `./test/report.docx`
11
+ let opt = {
12
+ fpInTemp: './src/templates/temp_tpc.docx', //docx模板, 未給則用w-html2docx內建模板
13
+ optMd2html: {
14
+ imgWidthMax: '500px',
15
+ },
16
+ optHtml2docx: {
17
+ imgRatioWidthMax: 0.5,
18
+ },
19
+ }
20
+
21
+ let r = await WMd2docx.cvMdToDocx(fpIn, fpOut, opt)
22
+ console.log(r)
23
+ // => { fpOutDocx: '...\\test\\report.docx', sizeDocx: 40960, sizeHtml: 61785, ms: 6532 }
24
+
25
+ w.fsDeleteFile(fpOut)
26
+
27
+ }
28
+ test()
29
+ .catch((err) => {
30
+ console.log('catch', err)
31
+ })
32
+
33
+
34
+ //node g.mjs
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "w-md2docx",
3
+ "version": "1.0.0",
4
+ "main": "dist/w-md2docx.umd.js",
5
+ "dependencies": {
6
+ "@hapi/hapi": "^21.4.10",
7
+ "w-html2docx": "^1.0.23",
8
+ "w-md2html": "^1.0.47",
9
+ "wsemi": "^1.8.80"
10
+ },
11
+ "devDependencies": {
12
+ "w-package-tools": "^1.1.15"
13
+ },
14
+ "scripts": {
15
+ "test": "mocha --parallel --timeout 60000",
16
+ "deploy": "gh-pages -d docs"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/yuda-lyu/w-md2docx.git"
21
+ },
22
+ "keywords": [
23
+ "package",
24
+ "tool",
25
+ "markdown",
26
+ "html",
27
+ "docx",
28
+ "nodejs"
29
+ ],
30
+ "author": "yuda-lyu(semisphere)",
31
+ "license": "MIT"
32
+ }
package/script.txt ADDED
@@ -0,0 +1,18 @@
1
+ #node toolg/addVersion.mjs
2
+ node toolg/modifyReadme.mjs
3
+
4
+ node toolg/cleanFolder.mjs
5
+ ./node_modules/.bin/jsdoc -c .jsdoc
6
+ node toolg/gDocsExams.mjs
7
+
8
+ node toolg/gDistRollup.mjs
9
+
10
+ git add . -A
11
+ git commit -m 'modify: '
12
+ git push origin master:master
13
+
14
+ npm run deploy
15
+
16
+ #npm test
17
+
18
+ #npm publish