w-md2docx 1.0.6 → 1.0.7

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.
@@ -7,7 +7,7 @@ import cvMdTo from '../src/cvMdTo.mjs'
7
7
  //cvMdTo之html路徑(不需Microsoft Word); docx路徑於unit-cvMdToDocx以Word守門測試
8
8
  describe('cvMdTo', function() {
9
9
 
10
- let fdTmp = path.resolve('./tmp/zt_cvMdTo')
10
+ let fdTmp = path.resolve('./test/_tmp/unit-cvMdTo')
11
11
  let svgB64 = fs.readFileSync('./test/cocktail.svg').toString('base64')
12
12
  let toHtml = (r) => Buffer.from(r.html.base64, 'base64').toString('utf8')
13
13
 
@@ -16,7 +16,7 @@ describe('cvMdTo', function() {
16
16
  })
17
17
 
18
18
  after(function() {
19
- fs.rmSync(fdTmp, { recursive: true, force: true })
19
+ fs.rmSync(fdTmp, { recursive: true, force: true, maxRetries: 10, retryDelay: 300 })
20
20
  })
21
21
 
22
22
  it('out=html: 回傳物件形狀與檔名', async function() {
@@ -1,22 +1,24 @@
1
1
  import fs from 'fs'
2
2
  import path from 'path'
3
3
  import zlib from 'zlib'
4
+ import { pathToFileURL } from 'url'
4
5
  import { execFileSync } from 'child_process'
5
6
  import assert from 'assert'
6
7
  import cvMdToDocx from '../src/cvMdToDocx.mjs'
7
8
  import cvMdTo from '../src/cvMdTo.mjs'
8
- import { getFpExe } from '../src/utils.mjs'
9
9
 
10
10
 
11
- //hasWord: 本機是否可調用 Microsoft Word(Windows + Word.Application COM 已註冊 + htmlToDocx.exe 存在)
12
- //why: docx 階段須真 Word, 無 Word 之機器僅測輸入檢查, 實轉區塊整段略過(同 w-html2docx 之 isWindows 守門)
11
+ //fdTmpRoot: 本測試檔專用暫存夾, 各describe於其下自建子夾並於after刪除
12
+ let fdTmpRoot = path.resolve('./test/_tmp/unit-cvMdToDocx')
13
+
14
+
15
+ //hasWord: 本機是否可調用 Microsoft Word(Windows + Word.Application COM 已註冊)
16
+ //why: docx 階段須真 Word, 無 Word 之機器實轉區塊整段略過;
17
+ // 不以 htmlToDocx.exe 是否存在為條件——缺檔時 w-html2docx 於轉檔時自動下載, 以其為條件會使剛安裝之機器漏測實轉
13
18
  function hasWord() {
14
19
  if (process.platform !== 'win32') {
15
20
  return false
16
21
  }
17
- if (getFpExe() === '') {
18
- return false
19
- }
20
22
  try {
21
23
  execFileSync('reg', ['query', 'HKCR\\Word.Application'], { stdio: 'ignore', windowsHide: true })
22
24
  return true
@@ -57,13 +59,15 @@ function readZipEntries(buf) {
57
59
 
58
60
  describe('cvMdToDocx 輸入檢查(不需Word)', function() {
59
61
 
62
+ let fpOut = path.resolve(fdTmpRoot, 'input', 'x.docx') //輸入檢查皆於寫檔前拒絕, 不會產生檔案
63
+
60
64
  it('fpInMd非有效字串', async function() {
61
- await assert.rejects(cvMdToDocx('', './tmp/x.docx'), (e) => e === 'fpInMd must be a non-empty string')
62
- await assert.rejects(cvMdToDocx(null, './tmp/x.docx'), (e) => e === 'fpInMd must be a non-empty string')
65
+ await assert.rejects(cvMdToDocx('', fpOut), (e) => e === 'fpInMd must be a non-empty string')
66
+ await assert.rejects(cvMdToDocx(null, fpOut), (e) => e === 'fpInMd must be a non-empty string')
63
67
  })
64
68
 
65
69
  it('fpInMd不存在', async function() {
66
- await assert.rejects(cvMdToDocx('./test/nope.md', './tmp/x.docx'), (e) => /^fpInMd\[.+nope\.md\] does not exist$/.test(e))
70
+ await assert.rejects(cvMdToDocx('./test/nope.md', fpOut), (e) => /^fpInMd\[.+nope\.md\] does not exist$/.test(e))
67
71
  })
68
72
 
69
73
  it('fpOutDocx非有效字串', async function() {
@@ -71,7 +75,7 @@ describe('cvMdToDocx 輸入檢查(不需Word)', function() {
71
75
  })
72
76
 
73
77
  it('fpInTemp不存在', async function() {
74
- await assert.rejects(cvMdToDocx('./test/report.md', './tmp/x.docx', { fpInTemp: './test/nope.docx' }), (e) => /^fpInTemp\[.+nope\.docx\] does not exist$/.test(e))
78
+ await assert.rejects(cvMdToDocx('./test/report.md', fpOut, { fpInTemp: './test/nope.docx' }), (e) => /^fpInTemp\[.+nope\.docx\] does not exist$/.test(e))
75
79
  })
76
80
 
77
81
  })
@@ -85,7 +89,7 @@ describe('cvMdToDocx 實轉(需Windows+Microsoft Word)', function() {
85
89
  return
86
90
  }
87
91
 
88
- let fdTmp = path.resolve('./tmp/zt_cvMdToDocx')
92
+ let fdTmp = path.resolve(fdTmpRoot, 'real')
89
93
  let fpIn = './test/report.md'
90
94
  let fpOut = path.resolve(fdTmp, 'report.docx')
91
95
  let fpOutHtml = path.resolve(fdTmp, 'report.html')
@@ -108,7 +112,7 @@ describe('cvMdToDocx 實轉(需Windows+Microsoft Word)', function() {
108
112
  })
109
113
 
110
114
  after(function() {
111
- fs.rmSync(fdTmp, { recursive: true, force: true })
115
+ fs.rmSync(fdTmp, { recursive: true, force: true, maxRetries: 10, retryDelay: 300 })
112
116
  })
113
117
 
114
118
  it('回傳物件: fpOutDocx/sizeDocx/sizeHtml/ms/fpOutHtml與實體檔一致', function() {
@@ -183,3 +187,162 @@ describe('cvMdToDocx 實轉(需Windows+Microsoft Word)', function() {
183
187
  })
184
188
 
185
189
  })
190
+
191
+
192
+ //轉檔器之定位與補齊由 w-html2docx 負責(WHtml2docx.mjs:158 → autoDownloadFiles), 上層不得以自身規則事前攔截
193
+ describe('cvMdToDocx 缺轉檔器時交由w-html2docx取得(不得於上層事前攔截)', function() {
194
+
195
+ //check, htmlToDocx.exe僅供Windows, 非Windows時w-html2docx於下載前即拒絕
196
+ if (process.platform !== 'win32') {
197
+ return
198
+ }
199
+
200
+ let fpInTemp = path.resolve('./src/templates/temp_tpc.docx')
201
+ let msgHint = 'w-html2docx resolves htmlToDocx.exe and its default template from cwd and downloads htmlToDocx.exe automatically when absent, please run from the project root that contains node_modules/w-html2docx'
202
+
203
+ after(function() {
204
+ fs.rmSync(fdTmpRoot, { recursive: true, force: true, maxRetries: 10, retryDelay: 300 })
205
+ })
206
+
207
+ it('缺檔時不於上層中斷, 由w-html2docx自動下載轉檔器後續行轉檔', async function() {
208
+ this.timeout(180000)
209
+
210
+ //fdCwd: 模擬工作目錄, 其下src/與node_modules/w-html2docx/src/皆無htmlToDocx.exe(即安裝時postinstall未執行)
211
+ let fdCwd = path.resolve(fdTmpRoot, 'autodl')
212
+ let fpMd = path.resolve(fdCwd, 'in.md')
213
+ let fpDocx = path.resolve(fdCwd, 'out.docx')
214
+ let fpExeDL = path.resolve(fdCwd, 'src', 'htmlToDocx.exe') //cwd無node_modules/w-html2docx/時之下載落點(autoDownloadFiles.mjs:66)
215
+ fs.rmSync(fdCwd, { recursive: true, force: true })
216
+ fs.mkdirSync(fdCwd, { recursive: true })
217
+ fs.writeFileSync(fpMd, '# 轉檔器自動下載\n\n段落', 'utf8')
218
+
219
+ let cwdOri = process.cwd()
220
+ let err = null
221
+ let r = null
222
+ process.chdir(fdCwd)
223
+ try {
224
+ assert.strict.equal(fs.existsSync(fpExeDL), false) //前置: 缺檔
225
+ r = await cvMdToDocx(fpMd, fpDocx, { fpInTemp })
226
+ .catch((e) => {
227
+ err = e
228
+ })
229
+ }
230
+ finally {
231
+ process.chdir(cwdOri)
232
+ }
233
+
234
+ //不得為上層事前檢查之拒絕(未呼叫w-html2docx即中斷)
235
+ assert.strict.equal(/htmlToDocx\.exe not found/.test(String(err)), false, `rejected by upper-layer pre-check: ${err}`)
236
+
237
+ //缺檔即自動下載: 轉檔器落地於下載落點
238
+ if (!fs.existsSync(fpExeDL)) {
239
+ //下載失敗(如離線)時僅能確認失敗來自docx階段(已交由w-html2docx), 無法確認落地
240
+ assert.strict.equal(/^Failed to convert html to docx: /.test(String(err)), true, String(err))
241
+ console.log(`[unit-cvMdToDocx] htmlToDocx.exe download failed, skip landing check: ${err}`)
242
+ this.skip()
243
+ }
244
+ assert.strict.equal(fs.statSync(fpExeDL).size > 1024 * 1024, true)
245
+
246
+ //下載後續行轉檔: 有Word則產出docx; 無Word則 w-html2docx(≥1.0.29) 以離開碼1 reject 並帶轉檔器回報之原因
247
+ //(其 srcPython/說明.txt 第9點: `error: com_error: (-2147221005, …)` 為Word未安裝), 本層加前綴後原樣傳出, 非轉檔器缺檔
248
+ if (err === null) {
249
+ assert.strict.equal(r.sizeDocx, fs.statSync(fpDocx).size)
250
+ assert.strict.equal(r.sizeDocx > 0, true)
251
+ }
252
+ else {
253
+ assert.strict.equal(hasWord(), false, `conversion failed although Word is available: ${err}`)
254
+ assert.strict.equal(/^Failed to convert html to docx: code\[1\]:\r?\nerror: com_error: /.test(String(err)), true, String(err))
255
+ }
256
+ })
257
+
258
+ it('缺檔且下載失敗時reject(不假成功), 不產出docx且中介html已清除', function() {
259
+ this.timeout(180000)
260
+
261
+ //fdCwd: 以子程序於此cwd啟動並經不可達之代理下載, 使w-html2docx之自動下載確定失敗(不需連網)
262
+ let fdCwd = path.resolve(fdTmpRoot, 'dlfail')
263
+ let fpMd = path.resolve(fdCwd, 'in.md')
264
+ let fpDocx = path.resolve(fdCwd, 'out.docx')
265
+ let fpExeDL = path.resolve(fdCwd, 'src', 'htmlToDocx.exe')
266
+ fs.rmSync(fdCwd, { recursive: true, force: true })
267
+ fs.mkdirSync(fdCwd, { recursive: true })
268
+ fs.writeFileSync(fpMd, '# 下載失敗\n\n段落', 'utf8')
269
+
270
+ let script = [
271
+ `import fs from 'fs'`,
272
+ `import os from 'os'`,
273
+ `let m = await import(process.env.T_MOD)`,
274
+ `let err = null`,
275
+ `await m.default(process.env.T_MD, process.env.T_DOCX, { fpInTemp: process.env.T_TEMP }).catch((e) => { err = e })`,
276
+ `let nHtml = fs.readdirSync(os.tmpdir()).filter((v) => v.startsWith('wmd2docx_' + process.pid + '_')).length`,
277
+ `process.stdout.write('@@RESULT@@' + JSON.stringify({ err, nHtml }))`,
278
+ `process.exit(0)`,
279
+ ].join('\n')
280
+ let out = execFileSync(process.execPath, ['--input-type=module', '-e', script], {
281
+ cwd: fdCwd,
282
+ env: {
283
+ ...process.env,
284
+ NODE_USE_ENV_PROXY: '1',
285
+ HTTPS_PROXY: 'http://127.0.0.1:1',
286
+ HTTP_PROXY: 'http://127.0.0.1:1',
287
+ T_MOD: pathToFileURL(path.resolve('./src/cvMdToDocx.mjs')).href,
288
+ T_MD: fpMd,
289
+ T_DOCX: fpDocx,
290
+ T_TEMP: fpInTemp,
291
+ },
292
+ windowsHide: true,
293
+ encoding: 'utf8',
294
+ })
295
+ let { err, nHtml } = JSON.parse(out.split('@@RESULT@@')[1])
296
+
297
+ //Node未支援NODE_USE_ENV_PROXY時下載會實際成功, 無法構成下載失敗之前置, 標為pending
298
+ if (fs.existsSync(fpExeDL)) {
299
+ console.log('[unit-cvMdToDocx] proxy env not honored by this Node version, skip download failure check')
300
+ this.skip()
301
+ }
302
+
303
+ //下載失敗才失敗: 由docx階段reject, 不假成功
304
+ assert.strict.equal(/^Failed to convert html to docx: /.test(String(err)), true, String(err))
305
+ assert.strict.equal(fs.existsSync(fpDocx), false)
306
+ //清理路徑: 未指定fpOutHtml時中介html於失敗時亦刪除
307
+ assert.strict.equal(nHtml, 0)
308
+ })
309
+
310
+ it('於不含node_modules/w-html2docx之cwd啟動且未給fpInTemp時, 保留w-html2docx原訊息並附cwd提示', function() {
311
+ this.timeout(180000)
312
+
313
+ //fdCwd: 以子程序於此cwd啟動, w-html2docx於模組載入時以cwd查找預設模板, 查無即拒絕(WHtml2docx.mjs:136, 早於下載)
314
+ let fdCwd = path.resolve(fdTmpRoot, 'wrongcwd')
315
+ let fpMd = path.resolve(fdCwd, 'in.md')
316
+ let fpDocx = path.resolve(fdCwd, 'out.docx')
317
+ fs.rmSync(fdCwd, { recursive: true, force: true })
318
+ fs.mkdirSync(fdCwd, { recursive: true })
319
+ fs.writeFileSync(fpMd, '# 錯誤工作目錄\n\n段落', 'utf8')
320
+
321
+ let script = [
322
+ `import(process.env.T_MOD).then(async (m) => {`,
323
+ ` let err = null`,
324
+ ` await m.default(process.env.T_MD, process.env.T_DOCX).catch((e) => { err = e })`,
325
+ ` process.stdout.write('@@RESULT@@' + JSON.stringify({ err }))`,
326
+ `})`,
327
+ ].join('\n')
328
+ let out = execFileSync(process.execPath, ['--input-type=module', '-e', script], {
329
+ cwd: fdCwd,
330
+ env: {
331
+ ...process.env,
332
+ T_MOD: pathToFileURL(path.resolve('./src/cvMdToDocx.mjs')).href,
333
+ T_MD: fpMd,
334
+ T_DOCX: fpDocx,
335
+ },
336
+ windowsHide: true,
337
+ encoding: 'utf8',
338
+ })
339
+ let { err } = JSON.parse(out.split('@@RESULT@@')[1])
340
+
341
+ //原訊息保留, 並附cwd與執行位置提示
342
+ let prefix = 'Failed to convert html to docx: can not find folder for tmp.docx (cwd='
343
+ assert.strict.equal(String(err).startsWith(prefix), true, String(err))
344
+ assert.strict.equal(String(err).toLowerCase().includes(`(cwd=${fdCwd.toLowerCase()}, `), true, String(err))
345
+ assert.strict.equal(String(err).endsWith(`, ${msgHint})`), true, String(err))
346
+ })
347
+
348
+ })
@@ -23,14 +23,14 @@ let sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
23
23
 
24
24
  describe('utils', function() {
25
25
 
26
- let fdTmp = path.resolve('./tmp/zt_utils')
26
+ let fdTmp = path.resolve('./test/_tmp/unit-utils')
27
27
 
28
28
  before(function() {
29
29
  fs.mkdirSync(fdTmp, { recursive: true })
30
30
  })
31
31
 
32
32
  after(function() {
33
- fs.rmSync(fdTmp, { recursive: true, force: true })
33
+ fs.rmSync(fdTmp, { recursive: true, force: true, maxRetries: 10, retryDelay: 300 })
34
34
  })
35
35
 
36
36
  describe('mime', function() {
@@ -182,7 +182,24 @@ describe('utils', function() {
182
182
  assert.strict.equal(r.cwd, path.resolve())
183
183
  assert.strict.equal(r.exePath, getFpExe())
184
184
  assert.strict.equal(r.exeFound, r.exePath !== '')
185
- assert.strict.equal(r.ready, r.isWindows && r.exeFound)
185
+ assert.strict.equal(r.ready, r.isWindows) //ready不以轉檔器存在為條件(缺檔於首次轉檔時由w-html2docx自動下載)
186
+ })
187
+
188
+ it('轉檔器缺檔時exeFound為false但ready不因此為false', function() {
189
+ let fdCwd = path.resolve(fdTmp, 'noexe') //其下src/與node_modules/w-html2docx/src/皆無htmlToDocx.exe
190
+ fs.mkdirSync(fdCwd, { recursive: true })
191
+ let cwdOri = process.cwd()
192
+ let r = null
193
+ process.chdir(fdCwd)
194
+ try {
195
+ r = checkDocxReady()
196
+ }
197
+ finally {
198
+ process.chdir(cwdOri)
199
+ }
200
+ assert.strict.equal(r.exePath, '')
201
+ assert.strict.equal(r.exeFound, false)
202
+ assert.strict.equal(r.ready, process.platform === 'win32')
186
203
  })
187
204
 
188
205
  })