utility2
this zero-dependency package will provide a collection of high-level functions to to build, test, and deploy webapps
live web demo
git-branch : | master | beta | alpha |
---|---|---|---|
test-server-github : | |||
test-server-heroku : | |||
test-report : | |||
coverage : | |||
build-artifacts : |
table of contents
- cdn download
- documentation
- quickstart standalone app
- quickstart example.js
- extra screenshots
- package.json
- changelog of last 50 commits
- internal build script
- misc
cdn download
documentation
cli help
api doc
todo
- jslint - remove ternary-operator/newline comment preceding bra
- migrate browser-testing from electron to headless-chromium
- rename var value to val
- replace uglifyjs-lite with terser-lite (v2.8.29)
- jslint - remove bad_property_a and unexpected_a hacks
- jslint - sort nested switch-statements
- add default testCase _testCase_cliRun_help
- merge class _http.IncomingMessage -> _http.ServerResponse
- integrate db-lite and github-crud into a cloud-based db on github
- add server stress-test using puppeteer
- none
changelog 2019.8.21
- npm publish 2019.8.21
- jslint - remove allow-method-chain-newline hack
- jslint - remove autofix - autofix-js-braket - remove leading-whitespace from bra
- jslint - internalize hacks to function warn_at_extra
- jslint - unhack const, let from var
- jslint - upgrade to jslint edition 2019.8.3
- jslint - add async/await support
- jslint - remove autofix-js-whitespace - ...}()); to ...}());\n\n\n\n
- rename coverage-hack to hack-istanbul, gotoNext to gotoNext, gotoState to gotoState, jslint-hack to hack-jslint
- istanbul - add cli-command report
- add files lib.puppeteer.js, raw.puppeteer.js
- istanbul - fix pragma-istanbul-ignore in acorn
- none
this package requires
- darwin or linux os
quickstart standalone app
to run this example, follow instruction in script below
# example.sh # this shell script will download and run a web-demo of utility2 as a standalone app # 1. download standalone app curl -O https://kaizhu256.github.io/node-utility2/build..beta..travis-ci.org/app/assets.app.js# 2. run standalone app PORT=8081 node ./assets.app.js# 3. open a browser to http://127.0.0.1:8081 and play with web-demo # 4. edit file assets.app.js to suit your needs
output from browser
output from shell
quickstart example.js
to run this example, follow the instruction in the script below
/*example.js this script will demo automated browser-tests with coverage(via electron and istanbul) instruction 1. save this script as example.js 2. run the shell-command: $ npm install utility2 electron-lite && \ PATH="$(pwd)/node_modules/.bin:$PATH" \ PORT=8081 \ npm_config_mode_coverage=utility2 \ node_modules/.bin/utility2 test example.js 3. view test-report in ./tmp/build/test-report.html 4. view coverage in ./tmp/build/coverage.html/index.html*/ /* istanbul instrument in package utility2 *//* istanbul ignore next *//* jslint utility2:true */ { "use strict"; var consoleError; var local; // init globalThis { try globalThis = Function"return this"; // jslint ignore:line catch ignore {} }; globalThisglobalThis = globalThis; // init debug_inline if !globalThis"debug\u0049nline" consoleError = consoleerror; globalThis"debug\u0049nline" = { /* * this function will both print <arguments> to stderr * and return <arguments>[0] */ var argList; argList = Array; // jslint ignore:line // debug arguments globalThis"debug\u0049nlineArguments" = argList; ; consoleError; ; // return arg0 for inspection return argList0; }; // init local local = {}; locallocal = local; globalThisglobalLocal = local; // init isBrowser localisBrowser = typeof window === "object" && window === globalThis && typeof windowXMLHttpRequest === "function" && windowdocument && typeof windowdocumentquerySelector === "function" ; // init function local { /* * this function will throw err.<message> if <passed> is falsy */ var err; if passed return; err = // ternary-operator message && typeof messagemessage === "string" && typeof messagestack === "string" // if message is errObj, then leave as is ? message : typeof message === "string" // if message is a string, then leave as is ? message // else JSON.stringify message : JSON ; throw err; }; local { /* * this function will if <fnc> exists, * them return <fnc>, * else return <nop> */ return fnc || localnop; }; local { /* * this function will return <value> */ return value; }; local { /* * this function will do nothing */ return; }; local { /* * this function will if items from <target> are * null, undefined, or empty-string, * then overwrite them with items from <source> */ target = target || {}; Object; return target; }; // require builtin if !localisBrowser localassert = ; localbuffer = ; localchild_process = ; localcluster = ; localcrypto = ; localdgram = ; localdns = ; localdomain = ; localevents = ; localfs = ; localhttp = ; localhttps = ; localnet = ; localos = ; localpath = ; localquerystring = ; localreadline = ; localrepl = ; localstream = ; localstring_decoder = ; localtimers = ; localtls = ; localtty = ; localurl = ; localutil = ; localvm = ; localzlib = ; }this; {"use strict"; // run shared js-env code - init-before {// init locallocal = globalThisutility2_rollup || globalThisutility2_utility2 || ;// init exportsglobalThislocal = local;// run test-serverlocal;// init assetslocalassetsDict"/assets.hello.txt" = "hello \ud83d\ude01\n";localassetsDict"/assets.index.template.html" = "";}; // run shared js-env code - function {local {/* * this function will test ajax's "200 ok" handling-behavior */ if !localisBrowser ; return; opt = {}; // test ajax-path "assets.hello.txt" local;}; local {/* * this function will test ajax's "404 not found" handling-behavior */ if !localisBrowser ; return; opt = {}; // test ajax-path "/undefined" local;}; local {/* * this function will test webpage's default handling-behavior */ if localisBrowser ; return; opt = modeCoverageMerge: true url: localserverLocalHost + "?modeTest=1" ; local;};}; /* istanbul ignore next */// run browser js-env code - init-test {if !localisBrowser return;// log stderr and stdout to #outputStdout1"error" "log";local;globalThisdomOnEventDelegateDict = local;localonEventDomDb = localdb && localdbonEventDomDb;local {/* * this function will run browser-tests */ }; local;}; /* istanbul ignore next */// run node js-env code - init-test {if localisBrowser return;// init exportsmoduleexports = local;/* validateLineSortedReset */// init assetslocalassetsDict = localassetsDict || {}; "assets.index.template.html" "assets.swgg.swagger.json" "assets.swgg.swagger.server.json";/* jslint ignore:start */localassetsDict"/assets.index.template.html" = '\<!doctype html>\n\<html lang="en">\n\<head>\n\<meta charset="utf-8">\n\<meta name="viewport" content="width=device-width, initial-scale=1">\n\<!-- "assets.utility2.template.html" -->\n\<title>{{env.npm_package_name}} ({{env.npm_package_version}})</title>\n\<style>\n\/* jslint utility2:true */\n\/*csslint\n\*/\n\/* csslint ignore:start */\n\*,\n\*:after,\n\*:before {\n\ box-sizing: border-box;\n\}\n\/* csslint ignore:end */\n\@keyframes uiAnimateShake {\n\0%,\n\50% {\n\ transform: translateX(10px);\n\}\n\100% {\n\ transform: translateX(0);\n\}\n\25%,\n\75% {\n\ transform: translateX(-10px);\n\}\n\}\n\@keyframes uiAnimateSpin {\n\0% {\n\ transform: rotate(0deg);\n\}\n\100% {\n\ transform: rotate(360deg);\n\}\n\}\n\a {\n\ overflow-wrap: break-word;\n\}\n\body {\n\ background: #eef;\n\ font-family: Arial, Helvetica, sans-serif;\n\ font-size: small;\n\ margin: 0 40px;\n\}\n\body > div,\n\body > form > div,\n\body > form > input,\n\body > form > pre,\n\body > form > .button,\n\body > form > .textarea,\n\body > input,\n\body > pre,\n\body > .button,\n\body > .textarea {\n\ margin-bottom: 20px;\n\ margin-top: 0;\n\}\n\body > form > input,\n\body > form > .button,\n\body > input,\n\body > .button {\n\ width: 20rem;\n\}\n\body > form > .textarea,\n\body > .textarea {\n\ height: 10rem;\n\ width: 100%;\n\}\n\body > .readonly {\n\ background: #ddd;\n\}\n\code,\n\pre,\n\.textarea {\n\ font-family: Consolas, Menlo, monospace;\n\ font-size: smaller;\n\}\n\pre {\n\ overflow-wrap: break-word;\n\ white-space: pre-wrap;\n\}\n\.button {\n\ background: #ddd;\n\ border: 1px solid #999;\n\ color: #000;\n\ cursor: pointer;\n\ display: inline-block;\n\ padding: 2px 5px;\n\ text-align: center;\n\ text-decoration: none;\n\}\n\.button:hover {\n\ background: #bbb;\n\}\n\.colorError {\n\ color: #d00;\n\}\n\.textarea {\n\ background: #fff;\n\ border: 1px solid #999;\n\ border-radius: 0;\n\ cursor: auto;\n\ overflow: auto;\n\ padding: 2px;\n\}\n\.uiAnimateShake {\n\ animation-duration: 500ms;\n\ animation-name: uiAnimateShake;\n\}\n\.uiAnimateSlide {\n\ overflow-y: hidden;\n\ transition: max-height ease-in 250ms, min-height ease-in 250ms, padding-bottom ease-in 250ms, padding-top ease-in 250ms;\n\}\n\.utility2FooterDiv {\n\ text-align: center;\n\}\n\.zeroPixel {\n\ border: 0;\n\ height: 0;\n\ margin: 0;\n\ padding: 0;\n\ width: 0;\n\}\n\</style>\n\</head>\n\<body>\n\<div id="ajaxProgressDiv1" style="background: #d00; height: 2px; left: 0; margin: 0; padding: 0; position: fixed; top: 0; transition: background 500ms, width 1500ms; width: 0%; z-index: 1;"></div>\n\<div class="uiAnimateSpin" style="animation: uiAnimateSpin 2s linear infinite; border: 5px solid #999; border-radius: 50%; border-top: 5px solid #7d7; display: none; height: 25px; vertical-align: middle; width: 25px;"></div>\n\<a class="zeroPixel" download="db.persistence.json" href="" id="dbExportA1"></a>\n\<input class="zeroPixel" data-onevent="onEventDomDb" data-onevent-db="dbImportInput" type="file">\n\<script>\n\/* jslint utility2:true */\n\// init domOnEventWindowOnloadTimeElapsed\n\(function () {\n\/*\n\ * this function will measure and print time-elapsed for window.onload\n\ */\n\ "use strict";\n\ if (window.domOnEventWindowOnloadTimeElapsed) {\n\ return;\n\ }\n\ window.domOnEventWindowOnloadTimeElapsed = Date.now() + 100;\n\ window.addEventListener("load", function () {\n\ setTimeout(function () {\n\ window.domOnEventWindowOnloadTimeElapsed = (\n\ Date.now()\n\ - window.domOnEventWindowOnloadTimeElapsed\n\ );\n\ console.error(\n\ "domOnEventWindowOnloadTimeElapsed = "\n\ + window.domOnEventWindowOnloadTimeElapsed\n\ );\n\ }, 100);\n\ });\n\}());\n\\n\\n\\n\// init domOnEventDelegateDict\n\(function () {\n\/*\n\ * this function will handle delegated dom-event\n\ */\n\ "use strict";\n\ var timerTimeoutDict;\n\ if (window.domOnEventDelegateDict) {\n\ return;\n\ }\n\ window.domOnEventDelegateDict = {};\n\ timerTimeoutDict = {};\n\ window.domOnEventDelegateDict.domOnEventDelegate = function (evt) {\n\ evt.targetOnEvent = evt.target.closest(\n\ "[data-onevent]"\n\ );\n\ if (\n\ !evt.targetOnEvent\n\ || evt.targetOnEvent.dataset.onevent === "domOnEventNop"\n\ || evt.target.closest(\n\ ".disabled, .readonly"\n\ )\n\ ) {\n\ return;\n\ }\n\ // rate-limit high-frequency-event\n\ switch (evt.type) {\n\ case "keydown":\n\ case "keyup":\n\ // filter non-input keyboard-event\n\ if (!evt.target.closest(\n\ "input, option, select, textarea"\n\ )) {\n\ return;\n\ }\n\ if (timerTimeoutDict[evt.type] !== true) {\n\ timerTimeoutDict[evt.type] = timerTimeoutDict[\n\ evt.type\n\ ] || setTimeout(function () {\n\ timerTimeoutDict[evt.type] = true;\n\ window.domOnEventDelegateDict.domOnEventDelegate(evt);\n\ }, 50);\n\ return;\n\ }\n\ timerTimeoutDict[evt.type] = null;\n\ break;\n\ }\n\ switch (evt.targetOnEvent.tagName) {\n\ case "BUTTON":\n\ case "FORM":\n\ evt.preventDefault();\n\ break;\n\ }\n\ evt.stopPropagation();\n\ window.domOnEventDelegateDict[evt.targetOnEvent.dataset.onevent](\n\ evt\n\ );\n\ };\n\ window.domOnEventDelegateDict.domOnEventResetOutput = function () {\n\ document.querySelectorAll(\n\ ".onevent-reset-output"\n\ ).forEach(function (elem) {\n\ switch (elem.tagName) {\n\ case "INPUT":\n\ case "TEXTAREA":\n\ elem.value = "";\n\ break;\n\ case "PRE":\n\ elem.textContent = "";\n\ break;\n\ default:\n\ elem.innerHTML = "";\n\ }\n\ });\n\ };\n\ // init event-handling\n\ [\n\ "change",\n\ "click",\n\ "keydown",\n\ "submit"\n\ ].forEach(function (eventType) {\n\ document.addEventListener(\n\ eventType,\n\ window.domOnEventDelegateDict.domOnEventDelegate\n\ );\n\ });\n\}());\n\\n\\n\\n\// init timerIntervalAjaxProgressUpdate\n\(function () {\n\/*\n\ * this function will increment ajax-progress-bar\n\ * until webpage has loaded\n\ */\n\ "use strict";\n\ var ajaxProgressDiv1;\n\ var ajaxProgressState;\n\ var ajaxProgressUpdate;\n\ if (\n\ window.timerIntervalAjaxProgressUpdate\n\ || !document.querySelector(\n\ "#ajaxProgressDiv1"\n\ )\n\ ) {\n\ return;\n\ }\n\ ajaxProgressDiv1 = document.querySelector(\n\ "#ajaxProgressDiv1"\n\ );\n\ setTimeout(function () {\n\ ajaxProgressDiv1.style.width = "25%";\n\ });\n\ ajaxProgressState = 0;\n\ ajaxProgressUpdate = (\n\ window.local\n\ && window.local.ajaxProgressUpdate\n\ ) || function () {\n\ ajaxProgressDiv1.style.width = "100%";\n\ setTimeout(function () {\n\ ajaxProgressDiv1.style.background = "transparent";\n\ setTimeout(function () {\n\ ajaxProgressDiv1.style.width = "0%";\n\ }, 500);\n\ }, 1000);\n\ };\n\ window.timerIntervalAjaxProgressUpdate = setInterval(function () {\n\ ajaxProgressState += 1;\n\ ajaxProgressDiv1.style.width = Math.max(\n\ 100 - 75 * Math.exp(-0.125 * ajaxProgressState),\n\ ajaxProgressDiv1.style.width.slice(0, -1) | 0\n\ ) + "%";\n\ }, 1000);\n\ window.addEventListener("load", function () {\n\ clearInterval(window.timerIntervalAjaxProgressUpdate);\n\ ajaxProgressUpdate();\n\ });\n\}());\n\\n\\n\\n\// init domOnEventSelectAllWithinPre\n\(function () {\n\/*\n\ * this function will limit select-all within <pre tabIndex="0"> elements\n\ * https://stackoverflow.com/questions/985272/selecting-text-in-an-element-akin-to-highlighting-with-your-mouse\n\ */\n\ "use strict";\n\ if (window.domOnEventSelectAllWithinPre) {\n\ return;\n\ }\n\ window.domOnEventSelectAllWithinPre = function (evt) {\n\ var range;\n\ var selection;\n\ if (\n\ evt\n\ && evt.key === "a"\n\ && (evt.ctrlKey || evt.metaKey)\n\ && evt.target.closest(\n\ "pre"\n\ )\n\ ) {\n\ range = document.createRange();\n\ range.selectNodeContents(evt.target.closest(\n\ "pre"\n\ ));\n\ selection = window.getSelection();\n\ selection.removeAllRanges();\n\ selection.addRange(range);\n\ evt.preventDefault();\n\ }\n\ };\n\ // init event-handling\n\ document.addEventListener(\n\ "keydown",\n\ window.domOnEventSelectAllWithinPre\n\ );\n\}());\n\</script>\n\<h1>\n\<!-- utility2-comment\n\ <a\n\ {{#if env.npm_package_homepage}}\n\ href="{{env.npm_package_homepage}}"\n\ {{/if env.npm_package_homepage}}\n\ target="_blank"\n\ >\n\utility2-comment -->\n\ {{env.npm_package_name}} ({{env.npm_package_version}})\n\<!-- utility2-comment\n\ </a>\n\utility2-comment -->\n\</h1>\n\<h3>{{env.npm_package_description}}</h3>\n\<!-- utility2-comment\n\<a class="button" download href="assets.app.js">download standalone app</a><br>\n\<button class="button" data-onevent="testRunBrowser" id="testRunButton1">run internal test</button><br>\n\utility2-comment -->\n\\n\\n\\n\<label>edit or paste script below to cover and test</label>\n\<textarea class="textarea" data-onevent="testRunBrowser" id="inputTextarea1">\n\// remove comment below to disable jslint\n\/*jslint browser, devel*/\n\/*global window*/\n\(function () {\n\ "use strict";\n\ var testCaseDict;\n\ testCaseDict = {};\n\ testCaseDict.modeTest = 1;\n\\n\ // comment this testCase to disable failed error demo\n\ testCaseDict.testCase_failed_error_demo = function (opt, onError) {\n\ /*\n\ * this function will run a failed error demo\n\ */\n\ // hack-jslint\n\ window.utility2.nop(opt);\n\ onError(new Error("this is a failed error demo"));\n\ };\n\\n\ testCaseDict.testCase_passed_ajax_demo = function (opt, onError) {\n\ /*\n\ * this function will demo a passed ajax test\n\ */\n\ var data;\n\ opt = {url: "/"};\n\ // test ajax-req for main-page "/"\n\ window.utility2.ajax(opt, function (err, xhr) {\n\ try {\n\ // validate no err occurred\n\ console.assert(!err, err);\n\ // validate "200 ok" status\n\ console.assert(xhr.statusCode === 200, xhr.statusCode);\n\ // validate non-empty data\n\ data = xhr.responseText;\n\ console.assert(data && data.length > 0, data);\n\ onError();\n\ } catch (errCaught) {\n\ onError(errCaught);\n\ }\n\ });\n\ };\n\\n\ window.utility2.testRunDefault(testCaseDict);\n\}());\n\</textarea>\n\<button class="button" data-onevent="testRunBrowser" id="jslintAutofixButton1">jslint autofix</button><br>\n\<pre class= "colorError" id="outputJslintPre1" tabindex="0"></pre>\n\<label>instrumented-code</label>\n\<pre class="readonly textarea" id="outputCode1" tabindex="0"></pre>\n\<label>stderr and stdout</label>\n\<pre class="onevent-reset-output readonly textarea" id="outputStdout1" tabindex="0"></pre>\n\<div id="testReportDiv1"></div>\n\<div id="coverageReportDiv1"></div>\n\<!-- utility2-comment\n\{{#if isRollup}}\n\<script src="assets.app.js"></script>\n\{{#unless isRollup}}\n\utility2-comment -->\n\<script src="assets.utility2.lib.istanbul.js"></script>\n\<script src="assets.utility2.lib.jslint.js"></script>\n\<script src="assets.utility2.lib.db.js"></script>\n\<script src="assets.utility2.lib.marked.js"></script>\n\<script src="assets.utility2.lib.sjcl.js"></script>\n\<script src="assets.utility2.js"></script>\n\<script>window.utility2_onReadyBefore.counter += 1;</script>\n\<script src="jsonp.utility2.stateInit?callback=window.utility2.stateInit"></script>\n\<script src="assets.example.js"></script>\n\<script src="assets.test.js"></script>\n\<script>window.utility2_onReadyBefore();</script>\n\<!-- utility2-comment\n\{{/if isRollup}}\n\utility2-comment -->\n\<div class="utility2FooterDiv">\n\ [ this app was created with\n\ <a href="https://github.com/kaizhu256/node-utility2" target="_blank">utility2</a>\n\ ]\n\</div>\n\</body>\n\</html>\n\';/* jslint ignore:end *//* validateLineSortedReset *//* jslint ignore:start */localassetsDict"/assets.utility2.js" = localassetsDict"/assets.utility2.js" || localfs;/* jslint ignore:end *//* validateLineSortedReset */localassetsDict"/" = localassetsDict "/assets.index.template.html";localassetsDict"/assets.example.html" = localassetsDict"/";localassetsDict"/index.html" = localassetsDict"/";// init cliif module !== requiremain || globalThisutility2_rollup return;/* validateLineSortedReset */localassetsDict"/assets.example.js" = localassetsDict"/assets.example.js" || localfs;localassetsDict"/favicon.ico" = localassetsDict"/favicon.ico" || "";// if $npm_config_timeout_exit exists,// then exit this process after $npm_config_timeout_exit msif Numberprocessenvnpm_config_timeout_exit ;// start serverif globalThisutility2_serverHttp1 return;processenvPORT = processenvPORT || "8081";console;localhttp;};};
output from browser
output from shell
extra screenshots
-
https://kaizhu256.github.io/node-utility2/build/screenshot.buildCi.browser.%252Ftmp%252Fbuild%252Fapidoc.html.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.buildCi.browser.%252Ftmp%252Fbuild%252Fcoverage.lib.html.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.buildCi.browser.%252Ftmp%252Fbuild%252Ftest-report.html.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.deployGithub.browser.%252Fnode-utility2%252Fbuild%252Fapp%252Fassets.swgg.html.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.deployGithub.browser.%252Fnode-utility2%252Fbuild%252Fapp.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.deployGithubTest.browser.%252Fnode-utility2%252Fbuild%252Fapp.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.deployHeroku.browser.%252Fassets.swgg.html.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.deployHeroku.browser.%252F.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.deployHerokuTest.browser.%252F.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.npmTest.browser.%252F.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.testExampleJs.browser.%252F.png
-
https://kaizhu256.github.io/node-utility2/build/screenshot.testExampleSh.browser.%252F.png
package.json
changelog of last 50 commits
internal build script
- Dockerfile.base
# Dockerfile.base # docker build -f tmp/README.Dockerfile.base -t kaizhu256/node-utility2:base . # docker build -f "tmp/README.Dockerfile.$DOCKER_TAG" -t "$GITHUB_REPO:$DOCKER_TAG" . # https://hub.docker.com/_/node/ FROM debian:stable-slimMAINTAINER kai zhu <kaizhu256@gmail.com>VOLUME [ \ "/mnt", \ "/root", \ "/tmp", \ "/usr/share/doc", \ "/usr/share/man", \ "/var/cache", \ "/var/lib/apt", \ "/var/log", \ "/var/tmp" \]WORKDIR /tmp# install nodejs # https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions RUN # install electron-lite # COPY electron-*.zip /tmp # libasound.so.2: cannot open shared object file: No such file or directory # libgconf-2.so.4: cannot open shared object file: No such file or directory # libgtk-3.so.0: cannot open shared object file: No such file or directory # libnss3.so: cannot open shared object file: No such file or directory # libXss.so.1: cannot open shared object file: No such file or directory # libXtst.so.6: cannot open shared object file: No such file or directory RUN # install extra RUN
- Dockerfile.latest
# Dockerfile.latest FROM kaizhu256/node-utility2:baseMAINTAINER kai zhu <kaizhu256@gmail.com># install utility2 RUN # install elasticsearch-lite RUN
- Dockerfile.tmp
# Dockerfile.tmp FROM kaizhu256/node-utility2:baseMAINTAINER kai zhu <kaizhu256@gmail.com># install extra RUN # install binaryen RUN
- build_ci.sh
# build_ci.sh # this shell script will run the build for this package # run shBuildCi . ./lib.utility2.shshBuildCi
misc
- this package was created with utility2