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,516 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>ApiServer.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">ApiServer.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 os from 'os'
50
+ import path from 'path'
51
+ import { fileURLToPath } from 'url'
52
+ import { execFile } from 'child_process'
53
+ import get from 'lodash-es/get.js'
54
+ import isstr from 'wsemi/src/isstr.mjs'
55
+ import isestr from 'wsemi/src/isestr.mjs'
56
+ import isbol from 'wsemi/src/isbol.mjs'
57
+ import isobj from 'wsemi/src/isobj.mjs'
58
+ import isp0int from 'wsemi/src/isp0int.mjs'
59
+ import ispnum from 'wsemi/src/ispnum.mjs'
60
+ import cint from 'wsemi/src/cint.mjs'
61
+ import cdbl from 'wsemi/src/cdbl.mjs'
62
+ import fsIsFile from 'wsemi/src/fsIsFile.mjs'
63
+ import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
64
+ import Hapi from '@hapi/hapi'
65
+ import cvMdTo from './cvMdTo.mjs'
66
+ import { toErrText, checkDocxReady, getQueueSize, cleanWorkDir } from './utils.mjs'
67
+
68
+
69
+ //fdSelf (場景B: 內建模板隨模組所在資料夾, 不依啟動時之相對路徑漂移)
70
+ let fdSelf = path.dirname(fileURLToPath(import.meta.url))
71
+
72
+
73
+ //getWinwordCount: 取當前 WINWORD 行程數(診斷用)
74
+ //why: 殘留之 WINWORD 行程會鎖檔並使後續轉檔失敗, 健檢時一併呈現供判斷
75
+ function getWinwordCount() {
76
+ return new Promise((resolve) => {
77
+ if (process.platform !== 'win32') {
78
+ resolve(-1)
79
+ return
80
+ }
81
+ execFile('tasklist', ['/FI', 'IMAGENAME eq WINWORD.EXE', '/NH'], { windowsHide: true, timeout: 8000 }, (err, stdout) => {
82
+ if (err) {
83
+ resolve(-1)
84
+ return
85
+ }
86
+ let n = (String(stdout).match(/WINWORD\.EXE/gi) || []).length
87
+ resolve(n)
88
+ })
89
+ })
90
+ }
91
+
92
+
93
+ //toContentDisposition: 組附檔標頭(中文檔名以 RFC5987 之 filename* 提供)
94
+ function toContentDisposition(fileName) {
95
+ let ascii = fileName.replace(/[^\x20-\x7E]/g, '_').replace(/"/g, '')
96
+ return `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(fileName)}`
97
+ }
98
+
99
+
100
+ //isInputError: 判斷是否為輸入面錯誤(回 400), 其餘視為轉檔或環境錯誤(回 500)
101
+ //note: 前綴須與 cvMdTo/utils 之錯誤訊息一致
102
+ function isInputError(msg) {
103
+ return /^(md is empty|asset|templateName)/.test(msg)
104
+ }
105
+
106
+
107
+ /**
108
+ * 建立並啟動Markdown轉Html/Docx之API服務
109
+ *
110
+ * 以hapi提供三個端點:GET /api/health(健康檢查與環境狀態)、GET /api/selftest(實跑一次最小轉檔驗證本機Word可用)、POST /api/convert(轉檔主端點,JSON參數同cvMdTo,另可用templateName指定服務端模板或templateBase64夾帶模板;query帶download=1時單一格式直接回傳二進位檔)。
111
+ *
112
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
113
+ * @param {Integer} [opt.port=22000] 輸入服務埠號整數,給0則由系統自動配置(實際埠號見回傳之server.info.port),預設22000
114
+ * @param {String} [opt.host='0.0.0.0'] 輸入監聽位址字串,預設'0.0.0.0'
115
+ * @param {String} [opt.token=''] 輸入權杖字串,非空時所有/api/*須帶x-api-token標頭,預設''
116
+ * @param {Number} [opt.maxMb=256] 輸入請求大小上限數字,單位MB,預設256
117
+ * @param {String} [opt.dirTemplates=''] 輸入docx模板資料夾位置字串,未給則用本套件src/templates,預設''
118
+ * @param {String} [opt.dirWork=''] 輸入工作資料夾位置字串,未給則用系統暫存夾下之w-md2docx,預設''
119
+ * @param {String} [opt.templateDef='temp_tpc.docx'] 輸入預設模板檔名字串,預設'temp_tpc.docx'
120
+ * @param {Boolean} [opt.autoStart=true] 輸入是否立即啟動服務布林值,預設true
121
+ * @returns {Promise} 回傳Promise,resolve回傳{server,settings,getState,stop},其中server為hapi實例,getState為取環境狀態之async函數,stop為停止服務之async函數,reject回傳錯誤訊息
122
+ * @example
123
+ *
124
+ * import ApiServer from 'w-md2docx/src/ApiServer.mjs'
125
+ *
126
+ * let { server, settings } = await ApiServer({
127
+ * port: 22000,
128
+ * token: '',
129
+ * })
130
+ * console.log(`listening on ${server.info.uri}`, settings)
131
+ *
132
+ */
133
+ async function ApiServer(opt = {}) {
134
+
135
+ //port (0 為由系統自動配置, 供測試等場景避免撞埠; 實際埠號由 server.info.port 取得)
136
+ let port = get(opt, 'port', null)
137
+ if (!isp0int(port)) {
138
+ port = 22000
139
+ }
140
+ port = cint(port)
141
+
142
+ //host
143
+ let host = get(opt, 'host', '')
144
+ if (!isestr(host)) {
145
+ host = '0.0.0.0'
146
+ }
147
+ host = host.trim()
148
+
149
+ //token
150
+ let token = get(opt, 'token', '')
151
+ if (!isstr(token)) {
152
+ token = ''
153
+ }
154
+ token = token.trim()
155
+
156
+ //maxMb
157
+ let maxMb = get(opt, 'maxMb', null)
158
+ if (!ispnum(maxMb)) {
159
+ maxMb = 256
160
+ }
161
+ maxMb = cdbl(maxMb)
162
+ let maxBytes = Math.round(maxMb * 1024 * 1024)
163
+
164
+ //dirTemplates
165
+ let dirTemplates = get(opt, 'dirTemplates', '')
166
+ if (!isestr(dirTemplates)) {
167
+ dirTemplates = path.resolve(fdSelf, 'templates')
168
+ }
169
+ dirTemplates = path.resolve(dirTemplates)
170
+
171
+ //dirWork
172
+ let dirWork = get(opt, 'dirWork', '')
173
+ if (!isestr(dirWork)) {
174
+ dirWork = path.join(os.tmpdir(), 'w-md2docx')
175
+ }
176
+ dirWork = path.resolve(dirWork)
177
+
178
+ //templateDef
179
+ let templateDef = get(opt, 'templateDef', '')
180
+ if (!isestr(templateDef)) {
181
+ templateDef = 'temp_tpc.docx'
182
+ }
183
+ templateDef = templateDef.trim()
184
+
185
+ //autoStart
186
+ let autoStart = get(opt, 'autoStart', true)
187
+ if (!isbol(autoStart)) {
188
+ autoStart = true
189
+ }
190
+
191
+ //listTemplates: 列出可用之 docx 模板檔名
192
+ let listTemplates = () => {
193
+ if (!fsIsFolder(dirTemplates)) {
194
+ return []
195
+ }
196
+ return fs.readdirSync(dirTemplates).filter((v) => /\.docx$/i.test(v))
197
+ }
198
+
199
+ //getFpTemplateDef: 取預設模板位置(不存在則回空字串)
200
+ let getFpTemplateDef = () => {
201
+ let fp = path.resolve(dirTemplates, path.basename(templateDef))
202
+ return fsIsFile(fp) ? fp : ''
203
+ }
204
+
205
+ //pickTemplate: 決定本次使用之模板
206
+ //順序: 請求端夾帶(base64) > 請求指定之服務端模板名 > 服務端預設模板 > 空(交由底層自行決定)
207
+ //回傳 { fpInTemp, from, fpTmpDel } ; fpTmpDel 為須於用畢刪除之暫存模板檔
208
+ let pickTemplate = (inp) => {
209
+
210
+ //請求端夾帶
211
+ let b64 = get(inp, 'templateBase64', '')
212
+ if (!isestr(b64)) {
213
+ b64 = get(inp, 'template.base64', '')
214
+ }
215
+ if (isestr(b64)) {
216
+ b64 = b64.replace(/^data:[^;]+;base64,/, '')
217
+ fs.mkdirSync(dirWork, { recursive: true })
218
+ let fp = path.resolve(dirWork, `tmpl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}.docx`)
219
+ fs.writeFileSync(fp, Buffer.from(b64, 'base64'))
220
+ return { fpInTemp: fp, from: 'request', fpTmpDel: fp }
221
+ }
222
+
223
+ //指定服務端模板名
224
+ let fn = get(inp, 'templateName', '')
225
+ if (isestr(fn)) {
226
+ fn = fn.trim()
227
+ }
228
+ else {
229
+ fn = ''
230
+ }
231
+ if (fn !== '') {
232
+ let fp = path.resolve(dirTemplates, path.basename(fn)) //basename 化, 限定取用 templates 內之檔案
233
+ if (!fsIsFile(fp)) {
234
+ throw new Error(`templateName[${fn}] does not exist (available templates: ${listTemplates().join(', ') || 'none'})`)
235
+ }
236
+ return { fpInTemp: fp, from: 'server', fpTmpDel: '' }
237
+ }
238
+
239
+ //服務端預設模板
240
+ let fpDef = getFpTemplateDef()
241
+ if (fpDef !== '') {
242
+ return { fpInTemp: fpDef, from: 'default', fpTmpDel: '' }
243
+ }
244
+
245
+ return { fpInTemp: '', from: 'none', fpTmpDel: '' }
246
+ }
247
+
248
+ //getState: 取服務環境狀態
249
+ let getState = async () => {
250
+ let rd = checkDocxReady()
251
+ let nWinword = await getWinwordCount()
252
+ return {
253
+ platform: rd.platform,
254
+ node: process.version,
255
+ cwd: rd.cwd,
256
+ exeFound: rd.exeFound,
257
+ exePath: rd.exePath,
258
+ docxReady: rd.ready, //僅代表環境具備條件, 實際 Word 可用性須以 /api/selftest 驗證
259
+ templateDefault: getFpTemplateDef(),
260
+ templates: listTemplates(),
261
+ dirTemplates,
262
+ dirWork,
263
+ queue: getQueueSize(),
264
+ winwordProcesses: nWinword,
265
+ maxBytes,
266
+ tokenRequired: token !== '',
267
+ }
268
+ }
269
+
270
+ //convertCore: 依請求內容轉檔(模板由服務端決定後交由核心處理)
271
+ let convertCore = async (inp) => {
272
+ let rTemp = pickTemplate(inp)
273
+ try {
274
+ let keepTmp = get(inp, 'keepTmp', false)
275
+ let keepWork = get(inp, 'keepWork', false)
276
+ let r = await cvMdTo({
277
+ md: get(inp, 'md', ''),
278
+ mdBase64: get(inp, 'mdBase64', ''),
279
+ name: get(inp, 'name', ''),
280
+ out: get(inp, 'out', ''),
281
+ assets: get(inp, 'assets', []),
282
+ dirWork,
283
+ fpInTemp: rTemp.fpInTemp,
284
+ optMd2html: get(inp, 'optMd2html', {}),
285
+ optHtml2docx: get(inp, 'optHtml2docx', {}),
286
+ keepWork: (keepTmp === true || keepTmp === 'true' || keepWork === true || keepWork === 'true'),
287
+ })
288
+ return {
289
+ success: true,
290
+ ...r,
291
+ template: rTemp.from,
292
+ }
293
+ }
294
+ finally {
295
+ //刪除本次夾帶之暫存模板
296
+ if (rTemp.fpTmpDel !== '' &amp;&amp; fsIsFile(rTemp.fpTmpDel)) {
297
+ try {
298
+ fs.unlinkSync(rTemp.fpTmpDel)
299
+ }
300
+ catch (err) {
301
+ //殘檔被鎖, 留待下次清理
302
+ }
303
+ }
304
+ }
305
+ }
306
+
307
+ //清除逾時殘留之作業資料夾
308
+ let nClean = cleanWorkDir(dirWork)
309
+
310
+ //server
311
+ let server = Hapi.server({
312
+ port,
313
+ host,
314
+ routes: {
315
+ cors: true, //內網工具服務, 便於自瀏覽器或他機測試
316
+ payload: {
317
+ maxBytes,
318
+ },
319
+ },
320
+ })
321
+
322
+ //token 驗證(未設定時不啟用)
323
+ server.ext('onRequest', (req, h) => {
324
+ if (token === '') {
325
+ return h.continue
326
+ }
327
+ if (!req.path.startsWith('/api/')) {
328
+ return h.continue
329
+ }
330
+ let t = get(req, 'headers.x-api-token', '')
331
+ if (t !== token) {
332
+ return h.response({ success: false, error: 'unauthorized: a valid x-api-token header is required' }).code(401).takeover()
333
+ }
334
+ return h.continue
335
+ })
336
+
337
+ //apis
338
+ server.route([
339
+
340
+ {
341
+ //服務說明(供瀏覽器直接開啟確認服務存活)
342
+ method: 'GET',
343
+ path: '/',
344
+ handler: () => {
345
+ return {
346
+ service: 'w-md2docx',
347
+ description: 'API service for converting Markdown to HTML/DOCX',
348
+ endpoints: {
349
+ 'GET /api/health': 'health check and environment state',
350
+ 'GET /api/selftest': 'run a minimal conversion to verify that Microsoft Word is available',
351
+ 'POST /api/convert': 'main conversion endpoint (JSON)',
352
+ },
353
+ }
354
+ },
355
+ },
356
+
357
+ {
358
+ method: 'GET',
359
+ path: '/api/health',
360
+ handler: async () => {
361
+ let st = await getState()
362
+ return {
363
+ success: true,
364
+ ...st,
365
+ }
366
+ },
367
+ },
368
+
369
+ {
370
+ //實跑一次最小 md -> docx, 驗證本機 Word 確實可用(健檢僅檢查靜態條件, 無法確認 Word 能否被調用)
371
+ method: 'GET',
372
+ path: '/api/selftest',
373
+ handler: async (req, h) => {
374
+ let msStart = Date.now()
375
+ try {
376
+ let r = await convertCore({
377
+ md: '# selftest\n\nThis file is generated by the self test of the conversion service.\n',
378
+ name: 'selftest',
379
+ out: 'both',
380
+ })
381
+ return {
382
+ success: true,
383
+ ms: Date.now() - msStart,
384
+ htmlSize: get(r, 'html.size', 0),
385
+ docxSize: get(r, 'docx.size', 0),
386
+ template: r.template,
387
+ }
388
+ }
389
+ catch (err) {
390
+ let st = await getState()
391
+ return h.response({
392
+ success: false,
393
+ error: toErrText(err),
394
+ ms: Date.now() - msStart,
395
+ env: st,
396
+ }).code(500)
397
+ }
398
+ },
399
+ },
400
+
401
+ {
402
+ method: 'POST',
403
+ path: '/api/convert',
404
+ options: {
405
+ payload: {
406
+ maxBytes,
407
+ parse: true,
408
+ allow: 'application/json',
409
+ timeout: false, //允許大檔慢速上傳
410
+ },
411
+ timeout: {
412
+ socket: false, //轉檔耗時較長, 不以 socket 逾時中斷
413
+ },
414
+ },
415
+ handler: async (req, h) => {
416
+
417
+ let inp = req.payload
418
+ if (!isobj(inp)) {
419
+ return h.response({ success: false, error: 'parameters must be provided as a JSON object' }).code(400)
420
+ }
421
+
422
+ //download: 單一格式時可直接回傳二進位檔(便於 curl -o 與瀏覽器下載)
423
+ //why: 不合法組合(out='both')於轉檔前即擋下, 避免先跑完耗時之 Word 轉檔才回 400
424
+ let qd = get(req, 'query.download', '')
425
+ let download = (qd === '1' || qd === 'true')
426
+ if (download &amp;&amp; get(inp, 'out', '') === 'both') {
427
+ return h.response({ success: false, error: 'download only supports out of html or docx (single format)' }).code(400)
428
+ }
429
+
430
+ try {
431
+
432
+ let r = await convertCore(inp)
433
+
434
+ if (download) {
435
+ let one = null
436
+ if (r.out === 'docx' &amp;&amp; isobj(r.docx)) {
437
+ one = r.docx
438
+ }
439
+ else if (r.out === 'html' &amp;&amp; isobj(r.html)) {
440
+ one = r.html
441
+ }
442
+ if (one === null) {
443
+ return h.response({ success: false, error: 'download only supports out of html or docx (single format)' }).code(400)
444
+ }
445
+ return h.response(Buffer.from(one.base64, 'base64'))
446
+ .type(one.mime)
447
+ .header('content-disposition', toContentDisposition(one.fileName))
448
+ }
449
+
450
+ return r
451
+
452
+ }
453
+ catch (err) {
454
+ let msg = toErrText(err)
455
+ let code = isInputError(msg) ? 400 : 500
456
+ return h.response({ success: false, error: msg }).code(code)
457
+ }
458
+
459
+ },
460
+ },
461
+
462
+ ])
463
+
464
+ //autoStart
465
+ if (autoStart) {
466
+ await server.start()
467
+ }
468
+
469
+ //定期清除逾時殘留之作業資料夾
470
+ let idInterval = setInterval(() => {
471
+ cleanWorkDir(dirWork)
472
+ }, 3600 * 1000)
473
+ idInterval.unref()
474
+
475
+ //stop
476
+ let stop = async () => {
477
+ clearInterval(idInterval)
478
+ await server.stop()
479
+ }
480
+
481
+ return {
482
+ server,
483
+ getState,
484
+ stop,
485
+ settings: { port, host, token, maxMb, maxBytes, dirTemplates, dirWork, templateDef, nClean },
486
+ }
487
+
488
+ }
489
+
490
+
491
+ export default ApiServer
492
+ </code></pre>
493
+ </article>
494
+ </section>
495
+
496
+
497
+
498
+
499
+
500
+
501
+ </div>
502
+
503
+ <br class="clear">
504
+
505
+ <footer>
506
+ 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.
507
+ </footer>
508
+
509
+ <script>prettyPrint();</script>
510
+ <script src="scripts/polyfill.js"></script>
511
+ <script src="scripts/linenumber.js"></script>
512
+
513
+
514
+
515
+ </body>
516
+ </html>
@@ -0,0 +1,101 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>WMd2docx.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">WMd2docx.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import cvMdToDocx from './cvMdToDocx.mjs'
49
+ import cvMdTo from './cvMdTo.mjs'
50
+ import ApiServer from './ApiServer.mjs'
51
+ import ApiClient from './ApiClient.mjs'
52
+
53
+
54
+ /**
55
+ * Markdown轉Docx工具集
56
+ *
57
+ * 匯整四個入口:cvMdToDocx(md檔轉docx檔)、cvMdTo(md內容轉html/docx之base64)、ApiServer(hapi轉檔服務)、ApiClient(呼叫轉檔服務之用戶端)。
58
+ *
59
+ * @example
60
+ *
61
+ * import WMd2docx from 'w-md2docx/src/WMd2docx.mjs'
62
+ *
63
+ * let r = await WMd2docx.cvMdToDocx('./test/report.md', './test/report.docx')
64
+ * console.log(r)
65
+ * // => { fpOutDocx: '…', sizeDocx: 39856, sizeHtml: 12345, ms: 8342 }
66
+ *
67
+ */
68
+ let WMd2docx = {
69
+ cvMdToDocx,
70
+ cvMdTo,
71
+ ApiServer,
72
+ ApiClient,
73
+ }
74
+
75
+
76
+ export default WMd2docx
77
+ </code></pre>
78
+ </article>
79
+ </section>
80
+
81
+
82
+
83
+
84
+
85
+
86
+ </div>
87
+
88
+ <br class="clear">
89
+
90
+ <footer>
91
+ 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.
92
+ </footer>
93
+
94
+ <script>prettyPrint();</script>
95
+ <script src="scripts/polyfill.js"></script>
96
+ <script src="scripts/linenumber.js"></script>
97
+
98
+
99
+
100
+ </body>
101
+ </html>