wsemi 1.9.13 → 1.9.15
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/README.md +1 -1
- package/dist/wsemi.umd.js +7 -7
- package/dist/wsemi.umd.js.map +1 -1
- package/docs/-_class.mjs.html +1 -1
- package/docs/-_const.mjs.html +1 -1
- package/docs/-_detector.mjs.html +1 -1
- package/docs/-_optNum.mjs.html +1 -1
- package/docs/ab2blob.mjs.html +1 -1
- package/docs/ab2u8arr.mjs.html +1 -1
- package/docs/addExcelWorksheetFromData.mjs.html +1 -1
- package/docs/addMonth.mjs.html +1 -1
- package/docs/aes2str.mjs.html +1 -1
- package/docs/alive.mjs.html +106 -5
- package/docs/arr2dt.mjs.html +1 -1
- package/docs/arrAccum.mjs.html +1 -1
- package/docs/arrAdd.mjs.html +1 -1
- package/docs/arrAt.mjs.html +1 -1
- package/docs/arrDiff.mjs.html +1 -1
- package/docs/arrFilterByKeywords.mjs.html +1 -1
- package/docs/arrFilterByNum.mjs.html +1 -1
- package/docs/arrFilterByPnum.mjs.html +1 -1
- package/docs/arrFilterByPnumAndToLog.mjs.html +1 -1
- package/docs/arrFind.mjs.html +1 -1
- package/docs/arrGroupByMaxmin.mjs.html +1 -1
- package/docs/arrHas.mjs.html +1 -1
- package/docs/arrInsert.mjs.html +1 -1
- package/docs/arrLookupByMaxmin.mjs.html +1 -1
- package/docs/arrMax.mjs.html +1 -1
- package/docs/arrMin.mjs.html +1 -1
- package/docs/arrMulti.mjs.html +1 -1
- package/docs/arrNorm.mjs.html +1 -1
- package/docs/arrProduct.mjs.html +1 -1
- package/docs/arrPull.mjs.html +1 -1
- package/docs/arrPullAt.mjs.html +1 -1
- package/docs/arrReduce.mjs.html +1 -1
- package/docs/arrReverse.mjs.html +1 -1
- package/docs/arrSep.mjs.html +1 -1
- package/docs/arrSort.mjs.html +1 -1
- package/docs/asind.mjs.html +1 -1
- package/docs/atan2d.mjs.html +1 -1
- package/docs/atand.mjs.html +1 -1
- package/docs/attstr.mjs.html +1 -1
- package/docs/b642obj.mjs.html +1 -1
- package/docs/b642str.mjs.html +1 -1
- package/docs/b642u16arr.mjs.html +1 -1
- package/docs/b642u8arr.mjs.html +1 -1
- package/docs/binstr.mjs.html +1 -1
- package/docs/blob2ab.mjs.html +1 -1
- package/docs/blob2b64.mjs.html +1 -1
- package/docs/blob2str.mjs.html +1 -1
- package/docs/blob2u8arr.mjs.html +1 -1
- package/docs/blobs2b64s.mjs.html +1 -1
- package/docs/blobs2u8arrs.mjs.html +1 -1
- package/docs/bodyLog.mjs.html +1 -1
- package/docs/browserView.mjs.html +1 -1
- package/docs/browserViewFromBlob.mjs.html +1 -1
- package/docs/browserViewFromText.mjs.html +1 -1
- package/docs/browserViewFromU8arr.mjs.html +1 -1
- package/docs/bs2u8arr.mjs.html +1 -1
- package/docs/bufRead.mjs.html +1 -1
- package/docs/bufReadDbl.mjs.html +1 -1
- package/docs/bufWrite.mjs.html +1 -1
- package/docs/bufWriteDbl.mjs.html +1 -1
- package/docs/cache.mjs.html +3 -3
- package/docs/cacheBd.mjs.html +1 -1
- package/docs/cacheSt.mjs.html +2 -2
- package/docs/cbol.mjs.html +1 -1
- package/docs/cdbl.mjs.html +1 -1
- package/docs/ceil.mjs.html +1 -1
- package/docs/cfilesize.mjs.html +1 -1
- package/docs/cfinancial.mjs.html +1 -1
- package/docs/cint.mjs.html +1 -1
- package/docs/clearXSS.mjs.html +1 -1
- package/docs/clearXSSDyn.mjs.html +1 -1
- package/docs/color.mjs.html +1 -1
- package/docs/composeToTree.mjs.html +1 -1
- package/docs/convertToTree.mjs.html +1 -1
- package/docs/copyStrToClipboard.mjs.html +1 -1
- package/docs/cosd.mjs.html +1 -1
- package/docs/cotd.mjs.html +1 -1
- package/docs/createExcelWorkbook.mjs.html +1 -1
- package/docs/cscd.mjs.html +1 -1
- package/docs/cstr.mjs.html +1 -1
- package/docs/day2cht.mjs.html +1 -1
- package/docs/debounce.mjs.html +1 -1
- package/docs/delay.mjs.html +1 -1
- package/docs/detectXSS.mjs.html +1 -1
- package/docs/detectXSSDyn.mjs.html +1 -1
- package/docs/dig.mjs.html +1 -1
- package/docs/digExp.mjs.html +1 -1
- package/docs/domAlert.mjs.html +1 -1
- package/docs/domAppend.mjs.html +1 -1
- package/docs/domCancelEvent.mjs.html +1 -1
- package/docs/domCompareText.mjs.html +1 -1
- package/docs/domConvertToPic.mjs.html +1 -1
- package/docs/domConvertToPicDyn.mjs.html +1 -1
- package/docs/domDetect.mjs.html +1 -1
- package/docs/domDrag.mjs.html +1 -1
- package/docs/domDragBarAndScroll.mjs.html +1 -1
- package/docs/domDragPointer.mjs.html +1 -1
- package/docs/domDragPreview.mjs.html +1 -1
- package/docs/domDropFiles.mjs.html +1 -1
- package/docs/domElementsFromPoint.mjs.html +1 -1
- package/docs/domFadeIn.mjs.html +1 -1
- package/docs/domFadeOut.mjs.html +1 -1
- package/docs/domFind.mjs.html +1 -1
- package/docs/domFinds.mjs.html +1 -1
- package/docs/domGetAttr.mjs.html +1 -1
- package/docs/domGetBoudRect.mjs.html +1 -1
- package/docs/domGetBoudRectRefSelf.mjs.html +1 -1
- package/docs/domGetFileAccept.mjs.html +1 -1
- package/docs/domGetFiles.mjs.html +1 -1
- package/docs/domGetParents.mjs.html +1 -1
- package/docs/domGetPointFromEvent.mjs.html +1 -1
- package/docs/domGetScrollableParent.mjs.html +1 -1
- package/docs/domGetWindowSize.mjs.html +1 -1
- package/docs/domIsClientXYIn.mjs.html +1 -1
- package/docs/domIsFocused.mjs.html +1 -1
- package/docs/domIsRendered.mjs.html +1 -1
- package/docs/domIsStable.mjs.html +1 -1
- package/docs/domIsVisible.mjs.html +1 -1
- package/docs/domPrepend.mjs.html +1 -1
- package/docs/domRemove.mjs.html +1 -1
- package/docs/domRenderMarkdownMind.mjs.html +1 -1
- package/docs/domRenderMarkdownMindDyn.mjs.html +1 -1
- package/docs/domRipple.mjs.html +1 -1
- package/docs/domScrollIntoView.mjs.html +1 -1
- package/docs/domScrollTo.mjs.html +1 -1
- package/docs/domSetAttr.mjs.html +1 -1
- package/docs/domShowImages.mjs.html +1 -1
- package/docs/domShowImagesDyn.mjs.html +1 -1
- package/docs/domShowInputAndGetFiles.mjs.html +1 -1
- package/docs/domShowInputAndGetFilesU8Arrs.mjs.html +1 -1
- package/docs/domShowInputDatatime.mjs.html +1 -1
- package/docs/domTooltip.mjs.html +1 -1
- package/docs/domTriggerEvent.mjs.html +1 -1
- package/docs/domVirtualCreate.mjs.html +1 -1
- package/docs/domVirtualCreateQueue.mjs.html +1 -1
- package/docs/downloadExcelFileFromData.mjs.html +1 -1
- package/docs/downloadExcelFileFromDataDyn.mjs.html +1 -1
- package/docs/downloadFileFromB64.mjs.html +1 -1
- package/docs/downloadFileFromBlob.mjs.html +1 -1
- package/docs/downloadFileFromText.mjs.html +1 -1
- package/docs/downloadFileFromU8Arr.mjs.html +1 -1
- package/docs/dtmapping.mjs.html +1 -1
- package/docs/dtpick.mjs.html +1 -1
- package/docs/estimateTicks.mjs.html +1 -1
- package/docs/evEmit.mjs.html +2 -2
- package/docs/evEmitDelay.mjs.html +1 -1
- package/docs/evem.mjs.html +1 -1
- package/docs/examples/ex-blob2b64.html +1 -1
- package/docs/examples/ex-clearXSS.html +1 -1
- package/docs/examples/ex-clearXSSDyn.html +1 -1
- package/docs/examples/ex-colorConverter.html +1 -1
- package/docs/examples/ex-detectXSS.html +1 -1
- package/docs/examples/ex-detectXSSDyn.html +1 -1
- package/docs/examples/ex-domCompareText.html +1 -1
- package/docs/examples/ex-domConvertToPic.html +1 -1
- package/docs/examples/ex-domDrag.html +1 -1
- package/docs/examples/ex-domDropFiles.html +1 -1
- package/docs/examples/ex-domIsVisible.html +1 -1
- package/docs/examples/ex-domRenderMarkdownMind.html +1 -1
- package/docs/examples/ex-domRenderMarkdownMindDyn.html +1 -1
- package/docs/examples/ex-domRipple.html +1 -1
- package/docs/examples/ex-domScrollIntoView.html +1 -1
- package/docs/examples/ex-domScrollTo.html +1 -1
- package/docs/examples/ex-domShowImages.html +1 -1
- package/docs/examples/ex-domShowImagesDyn.html +1 -1
- package/docs/examples/ex-domShowInputAndGetFiles.html +1 -1
- package/docs/examples/ex-domTooltip.html +1 -1
- package/docs/examples/ex-getFileHash.html +1 -1
- package/docs/examples/ex-getUserAgent.html +1 -1
- package/docs/examples/ex-getUserAgentDyn.html +1 -1
- package/docs/examples/ex-html2str.html +1 -1
- package/docs/examples/ex-obj2u8arr-u8arr2obj.html +1 -1
- package/docs/examples/ex-openLink.html +1 -1
- package/docs/examples/ex-str2aes-aes2str.html +1 -1
- package/docs/examples/ex-str2b64-b642str.html +1 -1
- package/docs/examples/ex-strFindFuzz.html +1 -1
- package/docs/examples/ex-timeTZ2expire-timeTZ2past.html +1 -1
- package/docs/examples/ex-xlsx.html +1 -1
- package/docs/execCli.mjs.html +67 -33
- package/docs/execCliCore.mjs.html +4 -1
- package/docs/execCliJsonRpc.mjs.html +3 -3
- package/docs/execCliSession.mjs.html +24 -12
- package/docs/execJsCode.mjs.html +1 -1
- package/docs/execProcess.mjs.html +58 -35
- package/docs/execProcessKillPid.mjs.html +5 -3
- package/docs/filepathToTree.mjs.html +1 -1
- package/docs/files2data.mjs.html +1 -1
- package/docs/flattenToConn.mjs.html +1 -1
- package/docs/flattenTree.mjs.html +1 -1
- package/docs/floor.mjs.html +1 -1
- package/docs/fsBuildReadStreamText.mjs.html +1 -1
- package/docs/fsBuildWriteStreamText.mjs.html +1 -1
- package/docs/fsCleanFolder.mjs.html +1 -1
- package/docs/fsCleanFolderCore.mjs.html +1 -1
- package/docs/fsCopyFile.mjs.html +1 -1
- package/docs/fsCopyFileCore.mjs.html +1 -1
- package/docs/fsCopyFolder.mjs.html +1 -1
- package/docs/fsCopyFolderCore.mjs.html +1 -1
- package/docs/fsCreateFile.mjs.html +1 -1
- package/docs/fsCreateFileCore.mjs.html +1 -1
- package/docs/fsCreateFolder.mjs.html +1 -1
- package/docs/fsCreateFolderCore.mjs.html +1 -1
- package/docs/fsDeleteFile.mjs.html +1 -1
- package/docs/fsDeleteFileCore.mjs.html +1 -1
- package/docs/fsDeleteFileSafe.mjs.html +1 -1
- package/docs/fsDeleteFileSafeCore.mjs.html +1 -1
- package/docs/fsDeleteFolder.mjs.html +1 -1
- package/docs/fsDeleteFolderCore.mjs.html +1 -1
- package/docs/fsDeleteFolderSafe.mjs.html +1 -1
- package/docs/fsDeleteFolderSafeCore.mjs.html +1 -1
- package/docs/fsDownloadFile.mjs.html +1 -1
- package/docs/fsDownloadFileCore.mjs.html +1 -1
- package/docs/fsEvem.mjs.html +1 -1
- package/docs/fsExists.mjs.html +1 -1
- package/docs/fsExistsCore.mjs.html +1 -1
- package/docs/fsGetFileBasicHash.mjs.html +1 -1
- package/docs/fsGetFileBasicHashCore.mjs.html +1 -1
- package/docs/fsGetFileHash.mjs.html +1 -1
- package/docs/fsGetFileHashCore.mjs.html +1 -1
- package/docs/fsGetFileXxHash.mjs.html +1 -1
- package/docs/fsGetFileXxHashCore.mjs.html +1 -1
- package/docs/fsGetFilesInFolder.mjs.html +1 -1
- package/docs/fsGetFilesInFolderCore.mjs.html +1 -1
- package/docs/fsGetFilesWithHashInFolder.mjs.html +1 -1
- package/docs/fsGetFilesWithHashInFolderCore.mjs.html +1 -1
- package/docs/fsGetFolderBasicHash.mjs.html +1 -1
- package/docs/fsGetFolderBasicHashCore.mjs.html +1 -1
- package/docs/fsGetFoldersInFolder.mjs.html +1 -1
- package/docs/fsGetFoldersInFolderCore.mjs.html +1 -1
- package/docs/fsIsFile.mjs.html +1 -1
- package/docs/fsIsFileCore.mjs.html +1 -1
- package/docs/fsIsFolder.mjs.html +1 -1
- package/docs/fsIsFolderCore.mjs.html +1 -1
- package/docs/fsMergeFiles.mjs.html +1 -1
- package/docs/fsMergeFilesCore.mjs.html +1 -1
- package/docs/fsReadJson.mjs.html +1 -1
- package/docs/fsReadText.mjs.html +1 -1
- package/docs/fsReadTextCore.mjs.html +1 -1
- package/docs/fsRenameFile.mjs.html +1 -1
- package/docs/fsRenameFileCore.mjs.html +1 -1
- package/docs/fsRenameFolder.mjs.html +1 -1
- package/docs/fsRenameFolderCore.mjs.html +1 -1
- package/docs/fsSrlog.mjs.html +1 -1
- package/docs/fsSyncFolder.mjs.html +1 -1
- package/docs/fsSyncFolderCore.mjs.html +1 -1
- package/docs/fsTask.mjs.html +1 -1
- package/docs/fsTaskCp.mjs.html +1 -1
- package/docs/fsTreeFolder.mjs.html +1 -1
- package/docs/fsTreeFolderCore.mjs.html +1 -1
- package/docs/fsTreeFolderWithHash.mjs.html +1 -1
- package/docs/fsTreeFolderWithHashCore.mjs.html +1 -1
- package/docs/fsWatchFile.mjs.html +1 -1
- package/docs/fsWatchFolder.mjs.html +1 -1
- package/docs/fsWriteJson.mjs.html +1 -1
- package/docs/fsWriteText.mjs.html +1 -1
- package/docs/fsWriteTextCore.mjs.html +1 -1
- package/docs/genGradientColor.mjs.html +1 -1
- package/docs/genID.mjs.html +1 -1
- package/docs/genIDSeq.mjs.html +1 -1
- package/docs/genPm.mjs.html +1 -1
- package/docs/getBufferSize.mjs.html +1 -1
- package/docs/getClearPathName.mjs.html +1 -1
- package/docs/getCsvStrFromData.mjs.html +1 -1
- package/docs/getDataFromExcelFileU8Arr.mjs.html +1 -1
- package/docs/getDataFromExcelFileU8ArrDyn.mjs.html +1 -1
- package/docs/getEnv.mjs.html +1 -1
- package/docs/getErrorMessage.mjs.html +1 -1
- package/docs/getExcelU8ArrFromData.mjs.html +1 -1
- package/docs/getExcelU8ArrFromWorkbook.mjs.html +1 -1
- package/docs/getExcelWorkbookFromData.mjs.html +1 -1
- package/docs/getExcelWorkbookFromWorksheet.mjs.html +1 -1
- package/docs/getExcelWorksheetFromData.mjs.html +1 -1
- package/docs/getFileAccept.mjs.html +1 -1
- package/docs/getFileBasicHash.mjs.html +1 -1
- package/docs/getFileHash.mjs.html +1 -1
- package/docs/getFileName.mjs.html +1 -1
- package/docs/getFileNameExt.mjs.html +1 -1
- package/docs/getFileTrueName.mjs.html +1 -1
- package/docs/getFileType.mjs.html +1 -1
- package/docs/getFileXxHash.mjs.html +1 -1
- package/docs/getGlobal.mjs.html +1 -1
- package/docs/getObjSize.mjs.html +1 -1
- package/docs/getPathParent.mjs.html +1 -1
- package/docs/getTimeBetween.mjs.html +1 -1
- package/docs/getTimeFromUnit.mjs.html +1 -1
- package/docs/getTimeHp.mjs.html +1 -1
- package/docs/getTimeObject.mjs.html +1 -1
- package/docs/getTimeRange.mjs.html +1 -1
- package/docs/getUserAgent.mjs.html +1 -1
- package/docs/getUserAgentDyn.mjs.html +1 -1
- package/docs/getltdtkeys.mjs.html +1 -1
- package/docs/global.html +7 -7
- package/docs/haskey.mjs.html +1 -1
- package/docs/html2str.mjs.html +1 -1
- package/docs/htmlDecode.mjs.html +1 -1
- package/docs/htmlEncode.mjs.html +1 -1
- package/docs/importResources.mjs.html +1 -1
- package/docs/index.html +1 -1
- package/docs/interp1.mjs.html +1 -1
- package/docs/isDate.mjs.html +1 -1
- package/docs/isDev.mjs.html +1 -1
- package/docs/isEle.mjs.html +1 -1
- package/docs/isEleExistByID.mjs.html +1 -1
- package/docs/isEmail.mjs.html +1 -1
- package/docs/isErr.mjs.html +1 -1
- package/docs/isIE.mjs.html +1 -1
- package/docs/isNarrow.mjs.html +1 -1
- package/docs/isStrHasCapital.mjs.html +1 -1
- package/docs/isStrHasLetter.mjs.html +1 -1
- package/docs/isStrHasLowerCase.mjs.html +1 -1
- package/docs/isStrHasNumber.mjs.html +1 -1
- package/docs/isStrHasSpace.mjs.html +1 -1
- package/docs/isStrHasSymbol.mjs.html +1 -1
- package/docs/isUserIdentify.mjs.html +1 -1
- package/docs/isUserName.mjs.html +1 -1
- package/docs/isUserPw.mjs.html +1 -1
- package/docs/isWindow.mjs.html +1 -1
- package/docs/isab.mjs.html +1 -1
- package/docs/isarr.mjs.html +1 -1
- package/docs/isarr0.mjs.html +1 -1
- package/docs/isarr1.mjs.html +1 -1
- package/docs/isbigint.mjs.html +1 -1
- package/docs/isblob.mjs.html +1 -1
- package/docs/isbol.mjs.html +1 -1
- package/docs/isday.mjs.html +1 -1
- package/docs/isearr.mjs.html +1 -1
- package/docs/iseobj.mjs.html +1 -1
- package/docs/iser.mjs.html +1 -1
- package/docs/isernot.mjs.html +1 -1
- package/docs/isestr.mjs.html +1 -1
- package/docs/isfile.mjs.html +1 -1
- package/docs/isfun.mjs.html +1 -1
- package/docs/isint.mjs.html +1 -1
- package/docs/ismonth.mjs.html +1 -1
- package/docs/isn0int.mjs.html +1 -1
- package/docs/isn0num.mjs.html +1 -1
- package/docs/isnan.mjs.html +1 -1
- package/docs/isnbr.mjs.html +1 -1
- package/docs/isnint.mjs.html +1 -1
- package/docs/isnnum.mjs.html +1 -1
- package/docs/isnull.mjs.html +1 -1
- package/docs/isnum.mjs.html +1 -1
- package/docs/isobj.mjs.html +1 -1
- package/docs/isobj0.mjs.html +1 -1
- package/docs/isp0int.mjs.html +1 -1
- package/docs/isp0num.mjs.html +1 -1
- package/docs/ispint.mjs.html +1 -1
- package/docs/ispm.mjs.html +1 -1
- package/docs/ispnum.mjs.html +1 -1
- package/docs/isstr.mjs.html +1 -1
- package/docs/isstr0.mjs.html +1 -1
- package/docs/istime.mjs.html +1 -1
- package/docs/istimeTZ.mjs.html +1 -1
- package/docs/istimems.mjs.html +1 -1
- package/docs/istimemsTZ.mjs.html +1 -1
- package/docs/isu16arr.mjs.html +1 -1
- package/docs/isu8arr.mjs.html +1 -1
- package/docs/isundefined.mjs.html +1 -1
- package/docs/j2o.mjs.html +1 -1
- package/docs/judge.mjs.html +1 -1
- package/docs/keysmat2ltdt.mjs.html +1 -1
- package/docs/ltdt2vrtsable.mjs.html +1 -1
- package/docs/ltdtDiff.mjs.html +1 -1
- package/docs/ltdtDiffByKey.mjs.html +1 -1
- package/docs/ltdtkeys2mat.mjs.html +1 -1
- package/docs/ltdtkeysheads2mat.mjs.html +1 -1
- package/docs/ltdtmapping.mjs.html +1 -1
- package/docs/ltdtmerge.mjs.html +1 -1
- package/docs/ltdtpick.mjs.html +1 -1
- package/docs/ltdtpull.mjs.html +1 -1
- package/docs/mat2ltdt.mjs.html +1 -1
- package/docs/matConcat.mjs.html +1 -1
- package/docs/matDiff.mjs.html +1 -1
- package/docs/matat.mjs.html +1 -1
- package/docs/now2str.mjs.html +1 -1
- package/docs/now2strp.mjs.html +1 -1
- package/docs/nowDay2str.mjs.html +1 -1
- package/docs/nowms2str.mjs.html +1 -1
- package/docs/nowms2strp.mjs.html +1 -1
- package/docs/o2j.mjs.html +1 -1
- package/docs/obj2b64.mjs.html +1 -1
- package/docs/obj2pb64.mjs.html +1 -1
- package/docs/obj2str.mjs.html +1 -1
- package/docs/obj2stru8arr.mjs.html +1 -1
- package/docs/obj2u8arr.mjs.html +1 -1
- package/docs/objSortBy.mjs.html +1 -1
- package/docs/objSortByKeys.mjs.html +1 -1
- package/docs/oo.mjs.html +1 -1
- package/docs/openLink.mjs.html +1 -1
- package/docs/pb642obj.mjs.html +1 -1
- package/docs/pmChain.mjs.html +1 -1
- package/docs/pmConvertResolve.mjs.html +1 -1
- package/docs/pmDebounce.mjs.html +1 -1
- package/docs/pmHook.mjs.html +1 -1
- package/docs/pmHookReject.mjs.html +1 -1
- package/docs/pmIni.mjs.html +1 -1
- package/docs/pmInvResolve.mjs.html +1 -1
- package/docs/pmKeyMutex.mjs.html +1 -1
- package/docs/pmMap.mjs.html +1 -1
- package/docs/pmQueue.mjs.html +1 -1
- package/docs/pmSeries.mjs.html +1 -1
- package/docs/pmThrottle.mjs.html +1 -1
- package/docs/pmTimeout.mjs.html +1 -1
- package/docs/preciseNum.mjs.html +1 -1
- package/docs/pseudoRandom.mjs.html +1 -1
- package/docs/pseudoRandomIntRange.mjs.html +1 -1
- package/docs/pseudoRandomIntsNdpRange.mjs.html +1 -1
- package/docs/pseudoRandomIntsRange.mjs.html +1 -1
- package/docs/pseudoRandomRange.mjs.html +1 -1
- package/docs/pseudoRandomsRange.mjs.html +1 -1
- package/docs/queue.mjs.html +1 -1
- package/docs/random.mjs.html +1 -1
- package/docs/randomIntRange.mjs.html +1 -1
- package/docs/randomIntsNdpRange.mjs.html +1 -1
- package/docs/randomIntsRange.mjs.html +1 -1
- package/docs/randomRange.mjs.html +1 -1
- package/docs/randomsRange.mjs.html +1 -1
- package/docs/rang.mjs.html +1 -1
- package/docs/rep.mjs.html +1 -1
- package/docs/repObj.mjs.html +1 -1
- package/docs/replace.mjs.html +1 -1
- package/docs/replaceObj.mjs.html +1 -1
- package/docs/replacePlus.mjs.html +1 -1
- package/docs/round.mjs.html +1 -1
- package/docs/secd.mjs.html +1 -1
- package/docs/sep.mjs.html +1 -1
- package/docs/sepInt.mjs.html +1 -1
- package/docs/setIntervalHp.mjs.html +1 -1
- package/docs/setTimeoutHp.mjs.html +1 -1
- package/docs/sind.mjs.html +1 -1
- package/docs/split.mjs.html +1 -1
- package/docs/str2aes.mjs.html +1 -1
- package/docs/str2b64.mjs.html +1 -1
- package/docs/str2hint.mjs.html +1 -1
- package/docs/str2md5.mjs.html +1 -1
- package/docs/str2obj.mjs.html +1 -1
- package/docs/str2sha.mjs.html +1 -1
- package/docs/str2sha512.mjs.html +1 -1
- package/docs/str2u8arr.mjs.html +1 -1
- package/docs/strCompare.mjs.html +1 -1
- package/docs/strCompareForVersion.mjs.html +1 -1
- package/docs/strDiff.mjs.html +1 -1
- package/docs/strDiffToHtml.mjs.html +1 -1
- package/docs/strFindFuzz.mjs.html +1 -1
- package/docs/strFindSimilar.mjs.html +1 -1
- package/docs/strTruncate.mjs.html +1 -1
- package/docs/strdelleft.mjs.html +1 -1
- package/docs/strdelright.mjs.html +1 -1
- package/docs/strleft.mjs.html +1 -1
- package/docs/strmid.mjs.html +1 -1
- package/docs/strright.mjs.html +1 -1
- package/docs/stru8arr2obj.mjs.html +1 -1
- package/docs/tand.mjs.html +1 -1
- package/docs/time2day.mjs.html +1 -1
- package/docs/time2hour.mjs.html +1 -1
- package/docs/time2min.mjs.html +1 -1
- package/docs/timeTZ2day.mjs.html +1 -1
- package/docs/timeTZ2expire.mjs.html +1 -1
- package/docs/timeTZ2hour.mjs.html +1 -1
- package/docs/timeTZ2min.mjs.html +1 -1
- package/docs/timeTZ2past.mjs.html +1 -1
- package/docs/timems2day.mjs.html +1 -1
- package/docs/timems2hour.mjs.html +1 -1
- package/docs/timems2min.mjs.html +1 -1
- package/docs/timems2time.mjs.html +1 -1
- package/docs/timemsTZ2day.mjs.html +1 -1
- package/docs/timemsTZ2expire.mjs.html +1 -1
- package/docs/timemsTZ2hour.mjs.html +1 -1
- package/docs/timemsTZ2min.mjs.html +1 -1
- package/docs/timemsTZ2past.mjs.html +1 -1
- package/docs/timemsTZ2time.mjs.html +1 -1
- package/docs/treeObj.mjs.html +1 -1
- package/docs/treeToFilepath.mjs.html +1 -1
- package/docs/trim.mjs.html +1 -1
- package/docs/u16arr2b64.mjs.html +1 -1
- package/docs/u16arr2u8arr.mjs.html +1 -1
- package/docs/u8arr2ab.mjs.html +1 -1
- package/docs/u8arr2b64.mjs.html +1 -1
- package/docs/u8arr2blob.mjs.html +1 -1
- package/docs/u8arr2bs.mjs.html +1 -1
- package/docs/u8arr2obj.mjs.html +1 -1
- package/docs/u8arr2str.mjs.html +1 -1
- package/docs/u8arr2u16arr.mjs.html +1 -1
- package/docs/urlParse.mjs.html +1 -1
- package/docs/verifyValue.mjs.html +1 -1
- package/docs/waitFun.mjs.html +1 -1
- package/docs/wsemi.html +24 -23
- package/package.json +2 -2
- package/script.txt +1 -1
- package/src/_execSpawnError.mjs +41 -0
- package/src/alive.mjs +105 -4
- package/src/cache.mjs +2 -2
- package/src/cacheSt.mjs +1 -1
- package/src/evEmit.mjs +1 -1
- package/src/execCli.mjs +66 -32
- package/src/execCliCore.mjs +3 -0
- package/src/execCliJsonRpc.mjs +2 -2
- package/src/execCliSession.mjs +23 -11
- package/src/execProcess.mjs +57 -34
- package/src/execProcessKillPid.mjs +4 -2
- package/test/_execSpawnError.test.mjs +81 -0
- package/test/alive.test.mjs +201 -0
- package/test/cache.test.mjs +26 -0
- package/test/cacheSt.test.mjs +40 -0
- package/test/execCli.test.mjs +95 -0
- package/test/execCliJsonRpc.test.mjs +27 -0
- package/test/execCliSession.test.mjs +82 -0
- package/test/execProcess.test.mjs +135 -3
- package/test/execProcessKillPid.test.mjs +34 -0
- package/test/tools/spawnEmfileRunner.mjs +55 -0
- package/toolfp/dist/wsemip.umd.js +1 -1
- package/toolparfor/dist/wpf.umd.js +1 -1
- package/toolparfor/dist/wpf_pmSeries.umd.js +1 -1
package/test/execCli.test.mjs
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import cp from 'child_process'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
2
6
|
import execCli from '../src/execCli.mjs'
|
|
3
7
|
|
|
4
8
|
|
|
@@ -357,4 +361,95 @@ describe(`execCli`, function() {
|
|
|
357
361
|
assert.strict.deepStrictEqual(r.stdout.length < 2000, true)
|
|
358
362
|
})
|
|
359
363
|
|
|
364
|
+
//--- 失敗回報: 無法啟動子進程時不reject, error不含args與env之值 ---
|
|
365
|
+
|
|
366
|
+
let SA = 'SECRET_ARG_7d1f' //機密標記, 置於args
|
|
367
|
+
let SE = 'SECRET_ENV_9a2b' //機密標記, 置於env
|
|
368
|
+
let argLong = SA + 'x'.repeat(200000) //win32超過命令列上限, posix超過單一參數上限, 皆為spawn同步拋錯
|
|
369
|
+
let argNul = SA + '\u0000x'
|
|
370
|
+
let fdSrc = fileURLToPath(new URL('../src/', import.meta.url))
|
|
371
|
+
let fpEmfileRunner = path.resolve('./test/tools/spawnEmfileRunner.mjs')
|
|
372
|
+
let fdTmp = path.resolve('./test/_tmp/execCli')
|
|
373
|
+
let fpCmd = path.join(fdTmp, 'zzfallback.cmd')
|
|
374
|
+
|
|
375
|
+
//catchErr, 回傳Promise之拒絕值, 未拒絕時回{ resolved }
|
|
376
|
+
let catchErr = async (fn) => {
|
|
377
|
+
try {
|
|
378
|
+
let r = await fn()
|
|
379
|
+
return { resolved: r }
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
return { rejected: err }
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
before(function() {
|
|
387
|
+
fs.mkdirSync(fdTmp, { recursive: true })
|
|
388
|
+
fs.writeFileSync(fpCmd, '@echo off\r\necho fallback %*\r\n') //無%dp0%\node_modules入口之.cmd, buildSpawnArgs走cmd.exe退路
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
after(function() {
|
|
392
|
+
fs.rmSync(fdTmp, { recursive: true, force: true })
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
it(`should resolve ok=false with pid undefined, without args and without retry when spawn throws synchronously`, async function() {
|
|
396
|
+
let r1 = await catchErr(() => execCli(nodeBin, ['-e', '1', argNul], { maxRetries: 2, retryDelayMs: 1 }))
|
|
397
|
+
let r2 = await catchErr(() => execCli(nodeBin, ['-e', '1', argLong], { maxRetries: 2, retryDelayMs: 1 }))
|
|
398
|
+
//P04a: 不reject, 結果物件之code為null、pid為undefined(同ENOENT), error為「碼: 訊息」且不含args
|
|
399
|
+
assert.strict.deepStrictEqual(['resolved' in r1, 'resolved' in r2], [true, true])
|
|
400
|
+
r1 = r1.resolved
|
|
401
|
+
r2 = r2.resolved
|
|
402
|
+
assert.strict.deepStrictEqual([r1.ok, r1.code, 'pid' in r1, r1.pid], [false, null, true, undefined])
|
|
403
|
+
assert.strict.deepStrictEqual(r1.error.startsWith('ERR_INVALID_ARG_VALUE: '), true)
|
|
404
|
+
assert.strict.deepStrictEqual(r1.error.includes(`'args[2]'`), true)
|
|
405
|
+
assert.strict.deepStrictEqual([r1.error.includes(SA), r1.error.includes('Received')], [false, false])
|
|
406
|
+
assert.strict.deepStrictEqual([r2.ok, r2.code, r2.error.includes(SA)], [false, null, false])
|
|
407
|
+
assert.strict.deepStrictEqual(r2.error.startsWith(process.platform === 'win32' ? 'ENAMETOOLONG: ' : ''), true)
|
|
408
|
+
//P04b: 同步錯誤不重試
|
|
409
|
+
assert.strict.deepStrictEqual([r1.attempts, r2.attempts], [1, 1])
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
it(`should not include env values in error when an env value contains a null byte`, async function() {
|
|
413
|
+
let r = await catchErr(() => execCli(nodeBin, ['-e', '1'], { env: { WSEMI_TEST_KEY: SE + '\u0000x' } }))
|
|
414
|
+
//P04c: 不reject, error保留變數名稱而不含其值
|
|
415
|
+
assert.strict.deepStrictEqual('resolved' in r, true)
|
|
416
|
+
assert.strict.deepStrictEqual(r.resolved.ok, false)
|
|
417
|
+
assert.strict.deepStrictEqual(r.resolved.error.includes('WSEMI_TEST_KEY'), true)
|
|
418
|
+
assert.strict.deepStrictEqual(r.resolved.error.includes(SE), false)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it(`should resolve ok=false with EMFILE without crashing when node cannot create stdio`, function() {
|
|
422
|
+
let p = cp.spawnSync(nodeBin, [fpEmfileRunner, fdSrc, 'execCli'], { encoding: 'utf8', windowsHide: true, timeout: 60000 })
|
|
423
|
+
let o = JSON.parse(p.stdout.trim().split(/\r?\n/).pop())
|
|
424
|
+
//P05: 不reject、不拋出未捕捉例外
|
|
425
|
+
assert.strict.deepStrictEqual(o.settle, 'resolve')
|
|
426
|
+
assert.strict.deepStrictEqual([o.value.ok, o.value.error.startsWith('EMFILE: ')], [false, true])
|
|
427
|
+
assert.strict.deepStrictEqual(o.hasSecret, false)
|
|
428
|
+
assert.strict.deepStrictEqual(o.uncaught, [])
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
it(`should treat a number in args like a string when a .cmd falls back to cmd.exe on win32`, async function() {
|
|
432
|
+
if (process.platform !== 'win32') {
|
|
433
|
+
this.skip()
|
|
434
|
+
}
|
|
435
|
+
let r = await catchErr(() => execCli(fpCmd, ['a', 123]))
|
|
436
|
+
let rs = await execCli(fpCmd, ['a', '123'])
|
|
437
|
+
//P04d: 不reject; P09: 非字串元素比照node轉為字串, 結果與給字串相同
|
|
438
|
+
assert.strict.deepStrictEqual('resolved' in r, true)
|
|
439
|
+
assert.strict.deepStrictEqual([r.resolved.ok, r.resolved.stdout], [true, rs.stdout])
|
|
440
|
+
assert.strict.deepStrictEqual(rs.stdout.includes('fallback'), true)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it(`should resolve ok=false instead of rejecting when building the .cmd fallback command throws on win32`, async function() {
|
|
444
|
+
if (process.platform !== 'win32') {
|
|
445
|
+
this.skip()
|
|
446
|
+
}
|
|
447
|
+
//無法轉為字串之元素使buildSpawnArgs於cmd.exe退路拋錯, 須與spawn同在try內
|
|
448
|
+
let r = await catchErr(() => execCli(fpCmd, ['a', Object.create(null)], { maxRetries: 2, retryDelayMs: 1 }))
|
|
449
|
+
//P04a、P04b
|
|
450
|
+
assert.strict.deepStrictEqual('resolved' in r, true)
|
|
451
|
+
assert.strict.deepStrictEqual([r.resolved.ok, r.resolved.code, r.resolved.pid, r.resolved.attempts], [false, null, undefined, 1])
|
|
452
|
+
assert.strict.deepStrictEqual(r.resolved.error.startsWith('UNKNOWN: '), true)
|
|
453
|
+
})
|
|
454
|
+
|
|
360
455
|
})
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import cp from 'child_process'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
2
5
|
import execCliJsonRpc from '../src/execCliJsonRpc.mjs'
|
|
3
6
|
|
|
4
7
|
|
|
@@ -140,4 +143,28 @@ describe(`execCliJsonRpc`, function() {
|
|
|
140
143
|
assert.strict.deepStrictEqual('params' in r.results.p, false)
|
|
141
144
|
})
|
|
142
145
|
|
|
146
|
+
//--- 失敗回報: error不含args之值, 系統資源用盡時不崩潰 ---
|
|
147
|
+
|
|
148
|
+
let SA = 'SECRET_ARG_7d1f' //機密標記, 置於args
|
|
149
|
+
let fdSrc = fileURLToPath(new URL('../src/', import.meta.url))
|
|
150
|
+
let fpEmfileRunner = path.resolve('./test/tools/spawnEmfileRunner.mjs')
|
|
151
|
+
|
|
152
|
+
it(`should not include args in error when spawn throws synchronously for a null byte`, async function() {
|
|
153
|
+
let r = await execCliJsonRpc(nodeBin, ['-e', '1', SA + '\u0000x'], [{ method: 'x' }])
|
|
154
|
+
//P06e
|
|
155
|
+
assert.strict.deepStrictEqual(r.ok, false)
|
|
156
|
+
assert.strict.deepStrictEqual(r.error.startsWith('x: ERR_INVALID_ARG_VALUE: '), true)
|
|
157
|
+
assert.strict.deepStrictEqual([r.error.includes(SA), r.error.includes('Received')], [false, false])
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it(`should report EMFILE without crashing when node cannot create stdio`, function() {
|
|
161
|
+
let p = cp.spawnSync(nodeBin, [fpEmfileRunner, fdSrc, 'execCliJsonRpc'], { encoding: 'utf8', windowsHide: true, timeout: 60000 })
|
|
162
|
+
let o = JSON.parse(p.stdout.trim().split(/\r?\n/).pop())
|
|
163
|
+
//P06f
|
|
164
|
+
assert.strict.deepStrictEqual(o.settle, 'resolve')
|
|
165
|
+
assert.strict.deepStrictEqual([o.value.ok, o.value.error.startsWith('x: EMFILE: ')], [false, true])
|
|
166
|
+
assert.strict.deepStrictEqual(o.hasSecret, false)
|
|
167
|
+
assert.strict.deepStrictEqual(o.uncaught, [])
|
|
168
|
+
})
|
|
169
|
+
|
|
143
170
|
})
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import cp from 'child_process'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
2
6
|
import execCliSession from '../src/execCliSession.mjs'
|
|
3
7
|
|
|
4
8
|
|
|
@@ -194,4 +198,82 @@ describe(`execCliSession`, function() {
|
|
|
194
198
|
assert.strict.deepStrictEqual(r.code, 0)
|
|
195
199
|
})
|
|
196
200
|
|
|
201
|
+
//--- 失敗回報: 不throw, error不含args與env之值 ---
|
|
202
|
+
|
|
203
|
+
let SA = 'SECRET_ARG_7d1f' //機密標記, 置於args
|
|
204
|
+
let SE = 'SECRET_ENV_9a2b' //機密標記, 置於env
|
|
205
|
+
let fdSrc = fileURLToPath(new URL('../src/', import.meta.url))
|
|
206
|
+
let fpEmfileRunner = path.resolve('./test/tools/spawnEmfileRunner.mjs')
|
|
207
|
+
let fdTmp = path.resolve('./test/_tmp/execCliSession')
|
|
208
|
+
let fpCmd = path.join(fdTmp, 'zzfallback.cmd')
|
|
209
|
+
|
|
210
|
+
before(function() {
|
|
211
|
+
fs.mkdirSync(fdTmp, { recursive: true })
|
|
212
|
+
fs.writeFileSync(fpCmd, '@echo off\r\necho fallback %*\r\n') //無%dp0%\node_modules入口之.cmd, buildSpawnArgs走cmd.exe退路
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
after(function() {
|
|
216
|
+
fs.rmSync(fdTmp, { recursive: true, force: true })
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it(`should not throw and should report errors without args or env values when spawn throws synchronously`, async function() {
|
|
220
|
+
let s1 = null
|
|
221
|
+
let s2 = null
|
|
222
|
+
assert.doesNotThrow(() => {
|
|
223
|
+
s1 = execCliSession(nodeBin, ['-e', '1', SA + '\u0000x'])
|
|
224
|
+
s2 = execCliSession(nodeBin, ['-e', '1'], { env: { WSEMI_TEST_KEY: SE + '\u0000x' } })
|
|
225
|
+
})
|
|
226
|
+
let r1 = await s1.stop()
|
|
227
|
+
let r2 = await s2.stop()
|
|
228
|
+
//P06a: 以stop()之結果物件回報, error保留參數名稱而不含其值
|
|
229
|
+
assert.strict.deepStrictEqual(r1.error.startsWith('ERR_INVALID_ARG_VALUE: '), true)
|
|
230
|
+
assert.strict.deepStrictEqual(r1.error.includes(`'args[2]'`), true)
|
|
231
|
+
assert.strict.deepStrictEqual([r1.error.includes(SA), r1.error.includes('Received')], [false, false])
|
|
232
|
+
assert.strict.deepStrictEqual([r2.error.includes('WSEMI_TEST_KEY'), r2.error.includes(SE)], [true, false])
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it(`should report EMFILE via stop() without throwing or crashing when node cannot create stdio`, function() {
|
|
236
|
+
let p = cp.spawnSync(nodeBin, [fpEmfileRunner, fdSrc, 'execCliSession'], { encoding: 'utf8', windowsHide: true, timeout: 60000 })
|
|
237
|
+
let o = JSON.parse(p.stdout.trim().split(/\r?\n/).pop())
|
|
238
|
+
//P06b
|
|
239
|
+
assert.strict.deepStrictEqual(o.settle, 'resolve')
|
|
240
|
+
assert.strict.deepStrictEqual(o.value.error.startsWith('EMFILE: '), true)
|
|
241
|
+
assert.strict.deepStrictEqual([o.value.exited, o.value.pid], [true, null])
|
|
242
|
+
assert.strict.deepStrictEqual(o.hasSecret, false)
|
|
243
|
+
assert.strict.deepStrictEqual(o.uncaught, [])
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it(`should not throw when a .cmd falls back to cmd.exe and args contain a number on win32`, async function() {
|
|
247
|
+
if (process.platform !== 'win32') {
|
|
248
|
+
this.skip()
|
|
249
|
+
}
|
|
250
|
+
let lines = []
|
|
251
|
+
let s = null
|
|
252
|
+
assert.doesNotThrow(() => {
|
|
253
|
+
s = execCliSession(fpCmd, ['a', 123], {
|
|
254
|
+
onLine: (line) => {
|
|
255
|
+
lines.push(line)
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
let r = await s.stop()
|
|
260
|
+
//P06c: 不throw; P09: 數字比照字串傳遞
|
|
261
|
+
assert.strict.deepStrictEqual([r.exited, r.code], [true, 0])
|
|
262
|
+
assert.strict.deepStrictEqual(lines.some((l) => l.includes('fallback') && l.includes('123')), true)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it(`should not throw when building the .cmd fallback command throws on win32`, async function() {
|
|
266
|
+
if (process.platform !== 'win32') {
|
|
267
|
+
this.skip()
|
|
268
|
+
}
|
|
269
|
+
//無法轉為字串之元素使buildSpawnArgs於cmd.exe退路拋錯, 須與spawn同在try內
|
|
270
|
+
let s = null
|
|
271
|
+
assert.doesNotThrow(() => {
|
|
272
|
+
s = execCliSession(fpCmd, ['a', Object.create(null)])
|
|
273
|
+
})
|
|
274
|
+
let r = await s.stop()
|
|
275
|
+
//P06c
|
|
276
|
+
assert.strict.deepStrictEqual([r.exited, r.pid, r.error.startsWith('UNKNOWN: ')], [true, null, true])
|
|
277
|
+
})
|
|
278
|
+
|
|
197
279
|
})
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
2
|
import cp from 'child_process'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'url'
|
|
3
5
|
import execProcess from '../src/execProcess.mjs'
|
|
4
6
|
|
|
5
7
|
|
|
@@ -73,16 +75,19 @@ describe(`execProcess`, function() {
|
|
|
73
75
|
assert.strict.deepStrictEqual(typeof err === 'string' && err.startsWith('code[2]'), true)
|
|
74
76
|
})
|
|
75
77
|
|
|
76
|
-
it(`should reject
|
|
78
|
+
it(`should reject a string spawn[ENOENT] without args when spawning a non-existent program`, async function() {
|
|
77
79
|
let err = null
|
|
78
80
|
try {
|
|
79
|
-
await execProcess('non_existent_command_xyz_12345', ['arg'])
|
|
81
|
+
await execProcess('non_existent_command_xyz_12345', ['arg', 'SECRET_ARG_7d1f'])
|
|
80
82
|
}
|
|
81
83
|
catch (e) {
|
|
82
84
|
err = e
|
|
83
85
|
}
|
|
84
86
|
// console.log('nonexistent err', err)
|
|
85
|
-
|
|
87
|
+
//P02a: 無法啟動子程序時reject字串`spawn[錯誤碼]:\n訊息`, 訊息含程式名, 不含args
|
|
88
|
+
assert.strict.deepStrictEqual(typeof err === 'string' && err.startsWith('spawn[ENOENT]:\n'), true)
|
|
89
|
+
assert.strict.deepStrictEqual(err.includes('non_existent_command_xyz_12345'), true)
|
|
90
|
+
assert.strict.deepStrictEqual(err.includes('SECRET_ARG_7d1f'), false)
|
|
86
91
|
})
|
|
87
92
|
|
|
88
93
|
it(`should ignore opt.timeout when value is invalid (e.g., -1)`, async function() {
|
|
@@ -346,4 +351,131 @@ describe(`execProcess`, function() {
|
|
|
346
351
|
assert.strict.deepStrictEqual(r1, r2)
|
|
347
352
|
})
|
|
348
353
|
|
|
354
|
+
//--- 失敗回報: 無法啟動子程序之拒絕值為字串且不含參數(規格句見JSDoc之returns) ---
|
|
355
|
+
|
|
356
|
+
let SA = 'SECRET_ARG_7d1f' //機密標記, 置於args
|
|
357
|
+
let progMissing = path.resolve('./test/_tmp/execProcess/zz_no_such_prog_123.exe') //不存在之絕對路徑, 不建立
|
|
358
|
+
let argLong = SA + 'x'.repeat(200000) //win32超過命令列上限(ENAMETOOLONG), posix超過單一參數上限, 皆為spawn同步拋錯
|
|
359
|
+
let argNul = SA + '\u0000x'
|
|
360
|
+
let fdSrc = fileURLToPath(new URL('../src/', import.meta.url))
|
|
361
|
+
let fpEmfileRunner = path.resolve('./test/tools/spawnEmfileRunner.mjs')
|
|
362
|
+
|
|
363
|
+
//catchErr, 回傳Promise之拒絕值, 未拒絕時回{ resolved }
|
|
364
|
+
let catchErr = async (fn) => {
|
|
365
|
+
try {
|
|
366
|
+
let r = await fn()
|
|
367
|
+
return { resolved: r }
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
return err
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
//runEmfile, 子行程內重現EMFILE後呼叫execProcess
|
|
375
|
+
let runEmfile = (opt) => {
|
|
376
|
+
let r = cp.spawnSync(nodeBin, [fpEmfileRunner, fdSrc, 'execProcess', JSON.stringify(opt)], { encoding: 'utf8', windowsHide: true, timeout: 60000 })
|
|
377
|
+
return JSON.parse(r.stdout.trim().split(/\r?\n/).pop())
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
for (let [name, opt] of [['spawn', {}], ['execFile', { mode: 'execFile' }], ['spawn with codeCmd auto', { codeCmd: 'auto' }]]) {
|
|
381
|
+
it(`should reject a string spawn[ENOENT] with the program path but without args for a missing program (${name})`, async function() {
|
|
382
|
+
let err = await catchErr(() => execProcess(progMissing, ['a', SA], opt))
|
|
383
|
+
//P02a、P02f
|
|
384
|
+
assert.strict.deepStrictEqual(typeof err, 'string')
|
|
385
|
+
assert.strict.deepStrictEqual(err.startsWith('spawn[ENOENT]:\n'), true)
|
|
386
|
+
assert.strict.deepStrictEqual(err.includes('zz_no_such_prog_123'), true)
|
|
387
|
+
assert.strict.deepStrictEqual(err.includes(SA), false)
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (let mode of ['spawn', 'execFile', 'exec']) {
|
|
392
|
+
it(`should reject a string spawn[...] without args when spawn throws synchronously for a too long argument (${mode})`, async function() {
|
|
393
|
+
let err = await catchErr(() => execProcess(nodeBin, ['-e', '1', argLong], { mode }))
|
|
394
|
+
//P02b、P02d
|
|
395
|
+
assert.strict.deepStrictEqual(typeof err, 'string')
|
|
396
|
+
assert.strict.deepStrictEqual(err.startsWith(process.platform === 'win32' ? 'spawn[ENAMETOOLONG]:\n' : 'spawn['), true)
|
|
397
|
+
assert.strict.deepStrictEqual(err.includes(SA), false)
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it(`should reject spawn[ERR_INVALID_ARG_VALUE] naming the argument but not its value for a null byte (${mode})`, async function() {
|
|
401
|
+
let err = await catchErr(() => execProcess(nodeBin, ['-e', '1', argNul], { mode }))
|
|
402
|
+
//P02c、P02d: node訊息之「Received 值」須去除, exec模式之參數名為command
|
|
403
|
+
assert.strict.deepStrictEqual(typeof err, 'string')
|
|
404
|
+
assert.strict.deepStrictEqual(err.startsWith('spawn[ERR_INVALID_ARG_VALUE]:\n'), true)
|
|
405
|
+
assert.strict.deepStrictEqual(err.includes(mode === 'exec' ? `'command'` : `'args[2]'`), true)
|
|
406
|
+
assert.strict.deepStrictEqual(err.includes(SA), false)
|
|
407
|
+
assert.strict.deepStrictEqual(err.includes('Received'), false)
|
|
408
|
+
})
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
it(`should reject spawn[ENOENT] without the command line when the shell is missing in exec mode`, function() {
|
|
412
|
+
if (process.platform !== 'win32') {
|
|
413
|
+
this.skip()
|
|
414
|
+
}
|
|
415
|
+
//子行程內改ComSpec, 不影響本行程; exec模式之node附帶參數為整條指令列
|
|
416
|
+
let url = pathToFileURL(path.join(fdSrc, 'execProcess.mjs')).href
|
|
417
|
+
let sc = `process.env.ComSpec='C:\\\\zz-no-such-dir\\\\cmd.exe';let m=await import(${JSON.stringify(url)});m.default(process.execPath,['-e','1',${JSON.stringify(SA)}],{mode:'exec'}).then((r)=>console.log(JSON.stringify({r})),(e)=>console.log(JSON.stringify({e:String(e),t:typeof e})))`
|
|
418
|
+
let r = cp.spawnSync(nodeBin, ['--input-type=module', '-e', sc], { encoding: 'utf8', windowsHide: true, timeout: 60000 })
|
|
419
|
+
let o = JSON.parse(r.stdout.trim())
|
|
420
|
+
//P02d
|
|
421
|
+
assert.strict.deepStrictEqual(o.t, 'string')
|
|
422
|
+
assert.strict.deepStrictEqual(o.e.startsWith('spawn[ENOENT]:\n'), true)
|
|
423
|
+
assert.strict.deepStrictEqual(o.e.includes(SA), false)
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it(`should reject a string when prog is an empty string or not a string`, async function() {
|
|
427
|
+
let err1 = await catchErr(() => execProcess('', ['-e', '1']))
|
|
428
|
+
let err2 = await catchErr(() => execProcess(undefined, ['-e', '1']))
|
|
429
|
+
//P02e
|
|
430
|
+
assert.strict.deepStrictEqual([typeof err1, typeof err2], ['string', 'string'])
|
|
431
|
+
assert.strict.deepStrictEqual(err1.startsWith('spawn[ERR_INVALID_ARG_VALUE]:\n'), true)
|
|
432
|
+
assert.strict.deepStrictEqual(err2.startsWith('spawn[ERR_INVALID_ARG_TYPE]:\n'), true)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it(`should reject a string when prog is quoted in spawn mode`, async function() {
|
|
436
|
+
let err = await catchErr(() => execProcess(`"${nodeBin}"`, ['-e', '1']))
|
|
437
|
+
//P07: spawn模式prog含引號時reject說明字串, 字誤已修
|
|
438
|
+
assert.strict.deepStrictEqual(typeof err, 'string')
|
|
439
|
+
assert.strict.deepStrictEqual(err.includes('does not need to add quotes'), true)
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
for (let [name, opt] of [['spawn', {}], ['execFile', { mode: 'execFile' }], ['exec', { mode: 'exec' }], ['spawn with codeCmd auto', { codeCmd: 'auto' }]]) {
|
|
443
|
+
it(`should reject spawn[EMFILE] without throwing or crashing when node cannot create stdio (${name})`, function() {
|
|
444
|
+
let o = runEmfile(opt)
|
|
445
|
+
//P03: 系統資源用盡時同無法啟動, 不拋出未捕捉例外
|
|
446
|
+
assert.strict.deepStrictEqual(o.settle, 'reject')
|
|
447
|
+
assert.strict.deepStrictEqual(typeof o.value === 'string' && o.value.startsWith('spawn[EMFILE]:\n'), true)
|
|
448
|
+
assert.strict.deepStrictEqual(o.hasSecret, false)
|
|
449
|
+
assert.strict.deepStrictEqual(o.uncaught, [])
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
//--- args: 空陣列與空字串代表無參數, 型別不符以reject回報 ---
|
|
454
|
+
|
|
455
|
+
//progNoArgs, 不帶參數即可執行並結束之系統程式
|
|
456
|
+
let progNoArgs = process.platform === 'win32' ? 'whoami' : 'pwd'
|
|
457
|
+
|
|
458
|
+
for (let mode of ['spawn', 'execFile', 'exec']) {
|
|
459
|
+
it(`should run the program without arguments when args is an empty array or an empty string (${mode})`, async function() {
|
|
460
|
+
let r1 = await catchErr(() => execProcess(progNoArgs, [], { mode, codeCmd: 'auto' }))
|
|
461
|
+
let r2 = await catchErr(() => execProcess(progNoArgs, '', { mode, codeCmd: 'auto' }))
|
|
462
|
+
//P11a、P11b: 照常執行並resolve輸出
|
|
463
|
+
assert.strict.deepStrictEqual('resolved' in r1 && r1.resolved.trim().length > 0, true)
|
|
464
|
+
assert.strict.deepStrictEqual('resolved' in r2 && r2.resolved.trim() === r1.resolved.trim(), true)
|
|
465
|
+
})
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
it(`should reject a string instead of throwing when args is neither an array nor a string`, async function() {
|
|
469
|
+
let rs = []
|
|
470
|
+
for (let v of [undefined, null, 123, {}, true]) {
|
|
471
|
+
let p = null
|
|
472
|
+
assert.doesNotThrow(() => {
|
|
473
|
+
p = execProcess(nodeBin, v)
|
|
474
|
+
})
|
|
475
|
+
rs.push(await catchErr(() => p))
|
|
476
|
+
}
|
|
477
|
+
//P11c
|
|
478
|
+
assert.strict.deepStrictEqual(rs, new Array(5).fill('args is not an array or string'))
|
|
479
|
+
})
|
|
480
|
+
|
|
349
481
|
})
|
|
@@ -104,4 +104,38 @@ describe(`execProcessKillPid`, function() {
|
|
|
104
104
|
assert.strict.deepStrictEqual(completed, true)
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
+
//--- 失敗回報: reject錯誤訊息字串, 與resolve之pid[N] killed對稱 ---
|
|
108
|
+
|
|
109
|
+
it(`should reject a string pid[N] kill failed when pid is not a number`, async function() {
|
|
110
|
+
let rs = []
|
|
111
|
+
for (let pid of ['abc', undefined]) {
|
|
112
|
+
try {
|
|
113
|
+
await execProcessKillPid(pid)
|
|
114
|
+
rs.push('resolved')
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
rs.push(e)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//P08a
|
|
121
|
+
assert.strict.deepStrictEqual(rs, ['pid[abc] kill failed: pid must be a number', 'pid[undefined] kill failed: pid must be a number'])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it(`should reject a string pid[N] kill failed when killing a non-existent pid on win32`, async function() {
|
|
125
|
+
//posix下tree-kill吞掉ESRCH而resolve(見上一案), 僅win32之taskkill失敗會reject
|
|
126
|
+
let fakePid = 999999
|
|
127
|
+
if (process.platform !== 'win32' || isAlive(fakePid)) {
|
|
128
|
+
this.skip()
|
|
129
|
+
}
|
|
130
|
+
let err = null
|
|
131
|
+
try {
|
|
132
|
+
await execProcessKillPid(fakePid)
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
err = e
|
|
136
|
+
}
|
|
137
|
+
//P08b
|
|
138
|
+
assert.strict.deepStrictEqual(typeof err === 'string' && err.startsWith(`pid[${fakePid}] kill failed: `), true)
|
|
139
|
+
})
|
|
140
|
+
|
|
107
141
|
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//spawnEmfileRunner.mjs: 於獨立子行程以替身重現node之EMFILE/ENFILE分支後呼叫指定函數, 印出結局與未捕捉例外(JSON一行)
|
|
2
|
+
// 替身依node lib/internal/child_process.js之ChildProcess.prototype.spawn: 對EMFILE、ENFILE設spawnfile與spawnargs後, 以nextTick觸發onexit(錯誤碼), 不建立stdin/stdout/stderr即return
|
|
3
|
+
// 須於子行程執行, 因改的是ChildProcess.prototype, 不可污染mocha之worker
|
|
4
|
+
//用法: node test/tools/spawnEmfileRunner.mjs <模組資料夾絕對路徑> <execProcess|execCli|execCliSession|execCliJsonRpc> [opt之JSON]
|
|
5
|
+
import util from 'util'
|
|
6
|
+
import cp from 'child_process'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import { pathToFileURL } from 'url'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
let [fdImpl, fn, sopt] = process.argv.slice(2)
|
|
12
|
+
let opt = sopt ? JSON.parse(sopt) : {}
|
|
13
|
+
let SA = 'SECRET_ARG_7d1f' //機密標記, 置於args
|
|
14
|
+
let out = { settle: null, value: null, uncaught: [] }
|
|
15
|
+
|
|
16
|
+
process.on('uncaughtException', (e) => {
|
|
17
|
+
out.uncaught.push(String(e && e.message))
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
//UV_EMFILE, 以系統錯誤對照表反查
|
|
21
|
+
let UV_EMFILE = [...util.getSystemErrorMap()].find(([k, v]) => v[0] === 'EMFILE')[0]
|
|
22
|
+
cp.ChildProcess.prototype.spawn = function(options) {
|
|
23
|
+
this.spawnfile = options.file
|
|
24
|
+
this.spawnargs = options.args
|
|
25
|
+
process.nextTick(() => {
|
|
26
|
+
this._handle.onexit(UV_EMFILE)
|
|
27
|
+
})
|
|
28
|
+
return UV_EMFILE
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let mod = (await import(pathToFileURL(path.join(fdImpl, `${fn}.mjs`)).href)).default
|
|
32
|
+
let args = ['-e', '1', SA]
|
|
33
|
+
try {
|
|
34
|
+
let r = null
|
|
35
|
+
if (fn === 'execCliSession') {
|
|
36
|
+
let s = mod(process.execPath, args, opt)
|
|
37
|
+
r = await s.stop()
|
|
38
|
+
}
|
|
39
|
+
else if (fn === 'execCliJsonRpc') {
|
|
40
|
+
r = await mod(process.execPath, args, [{ method: 'x' }], opt)
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
r = await mod(process.execPath, args, opt)
|
|
44
|
+
}
|
|
45
|
+
out.settle = 'resolve'
|
|
46
|
+
out.value = r
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
out.settle = 'reject'
|
|
50
|
+
out.value = (typeof err === 'string') ? err : `<${err && err.constructor && err.constructor.name}> ${err && err.message}`
|
|
51
|
+
}
|
|
52
|
+
await new Promise((resolve) => setTimeout(resolve, 300)) //等候延後之未捕捉例外(nextTick之'error'事件)
|
|
53
|
+
out.hasSecret = JSON.stringify(out).includes(SA)
|
|
54
|
+
console.log(JSON.stringify(out))
|
|
55
|
+
process.exit(0)
|