revamped console ui (#56)
* revamped console ui
- added spinner
- more consistent logging & colors
- highlight colors
- central log.js
* await shutdown promise so that Debugger.run() returns only after shutdown
* update readme with --silent mode
diff --git a/README.md b/README.md
index ed01193..e9ad006 100644
--- a/README.md
+++ b/README.md
@@ -517,6 +517,7 @@
Options:
-v, --verbose Verbose output. Logs activation parameters and result [boolean]
+ -s, --silent Silent. Only output logs from action container. [boolean]
--version Show version number [boolean]
-h, --help Show help [boolean]
```
diff --git a/index.js b/index.js
index 5f267f7..108f72a 100644
--- a/index.js
+++ b/index.js
@@ -21,32 +21,7 @@
const Debugger = require("./src/debugger");
const path = require("path");
const fs = require("fs");
-const debug = require('./src/debug');
-
-function enableConsoleColors() {
- // colorful console.error() and co
- let originalConsole = null;
- if (!console._logToFile) {
- originalConsole = {
- log: console.log,
- error: console.error,
- info: console.info,
- debug: console.debug
- };
- // overwrites console.log and co
- require('manakin').global;
- }
- return originalConsole;
-}
-
-function resetConsoleColors(originalConsole) {
- if (originalConsole) {
- console.log = originalConsole.log;
- console.error = originalConsole.error;
- console.info = originalConsole.info;
- console.debug = originalConsole.debug;
- }
-}
+const log = require('./src/log');
function getSupportedKinds() {
const kinds = [];
@@ -216,6 +191,11 @@
type: "boolean",
describe: "Verbose output. Logs activation parameters and result"
});
+ yargs.option("s", {
+ alias: "silent",
+ type: "boolean",
+ describe: "Silent. Only output logs from action container."
+ });
yargs.version(require("./package.json").version);
}
@@ -275,13 +255,9 @@
}
}
-function printErrorAndExit(err, argv) {
- console.log();
- if (argv.verbose) {
- console.error(err);
- } else {
- console.error("Error:", err.message);
- }
+function printErrorAndExit(err) {
+ log.log();
+ log.exception(err);
process.exit(1);
}
@@ -297,8 +273,8 @@
}
async function wskdebug(args, isCommandLine=false) {
- debug("wskdebug arguments:", args);
- const originalConsole = enableConsoleColors();
+ log.debug("wskdebug arguments:", args);
+ log.enableConsoleColors();
try {
const parser = getYargsParser();
@@ -315,6 +291,9 @@
return;
}
+ log.isVerbose = argv.verbose;
+ log.silent(argv.silent);
+
try {
const dbg = new Debugger(argv);
if (isCommandLine) {
@@ -325,14 +304,14 @@
} catch (e) {
if (isCommandLine) {
- printErrorAndExit(e, argv);
+ printErrorAndExit(e);
} else {
throw e;
}
}
} finally {
- resetConsoleColors(originalConsole);
+ log.resetConsoleColors();
}
}
diff --git a/package-lock.json b/package-lock.json
index ce21bad..ae04f84 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -185,6 +185,52 @@
"@babel/helper-validator-identifier": "^7.9.0",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
}
},
"@babel/parser": {
@@ -486,40 +532,12 @@
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
+ "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
"requires": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
- "requires": {
- "color-convert": "^1.9.0"
- }
- },
- "color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
- "requires": {
- "color-name": "1.1.3"
- }
- },
- "color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
- "dev": true
- }
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
}
},
"chardet": {
@@ -559,11 +577,15 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
- "dev": true,
"requires": {
"restore-cursor": "^3.1.0"
}
},
+ "cli-spinners": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.3.0.tgz",
+ "integrity": "sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w=="
+ },
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
@@ -681,6 +703,21 @@
"strip-bom": "^4.0.0"
}
},
+ "defaults": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
+ "requires": {
+ "clone": "^1.0.2"
+ },
+ "dependencies": {
+ "clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
+ }
+ }
+ },
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@@ -762,8 +799,7 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
- "dev": true
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"eslint": {
"version": "6.8.0",
@@ -816,6 +852,41 @@
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -833,6 +904,15 @@
"requires": {
"ansi-regex": "^4.1.0"
}
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
}
}
},
@@ -1227,8 +1307,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
- "dev": true
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-symbols": {
"version": "1.0.1",
@@ -1468,6 +1547,11 @@
"is-extglob": "^2.1.1"
}
},
+ "is-interactive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="
+ },
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -1799,9 +1883,49 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
"integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
- "dev": true,
"requires": {
"chalk": "^2.4.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
}
},
"make-dir": {
@@ -1821,8 +1945,7 @@
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "dev": true
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"minimatch": {
"version": "3.0.4",
@@ -2162,8 +2285,7 @@
"mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
- "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
- "dev": true
+ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"natural-compare": {
"version": "1.4.0",
@@ -2359,7 +2481,6 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
"integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
- "dev": true,
"requires": {
"mimic-fn": "^2.1.0"
}
@@ -2391,6 +2512,45 @@
"resolved": "https://registry.npmjs.org/opts/-/opts-1.2.7.tgz",
"integrity": "sha512-hwZhzGGG/GQ7igxAVFOEun2N4fWul31qE9nfBdCnZGQCB5+L7tN9xZ+94B4aUpLOJx/of3zZs5XsuubayQYQjA=="
},
+ "ora": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.3.tgz",
+ "integrity": "sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg==",
+ "requires": {
+ "chalk": "^3.0.0",
+ "cli-cursor": "^3.1.0",
+ "cli-spinners": "^2.2.0",
+ "is-interactive": "^1.0.0",
+ "log-symbols": "^3.0.0",
+ "mute-stream": "0.0.8",
+ "strip-ansi": "^6.0.0",
+ "wcwidth": "^1.0.1"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+ },
+ "supports-color": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
@@ -2600,7 +2760,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
- "dev": true,
"requires": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
@@ -2678,8 +2837,7 @@
"signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
- "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
- "dev": true
+ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
},
"slice-ansi": {
"version": "2.1.0",
@@ -2858,12 +3016,18 @@
"dev": true
},
"supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
"requires": {
- "has-flag": "^3.0.0"
+ "has-flag": "^4.0.0"
+ },
+ "dependencies": {
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+ }
}
},
"table": {
@@ -3047,6 +3211,14 @@
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
"dev": true
},
+ "wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
+ "requires": {
+ "defaults": "^1.0.3"
+ }
+ },
"whatwg-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
diff --git a/package.json b/package.json
index 27004d0..3c90c97 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
},
"scripts": {
"pretest": "npm install --no-save ngrok",
- "test": "nyc mocha test/**/*.test.js && test/install/test-npm-install.sh",
+ "test": "WSKDEBUG_SILENT=1 nyc mocha test/**/*.test.js && test/install/test-npm-install.sh",
"posttest": "eslint .",
"report-coverage": "nyc report --reporter=json && codecov -f coverage/coverage-final.json"
},
@@ -42,6 +42,7 @@
"file": "test/logfile.setup.js"
},
"dependencies": {
+ "chalk": "^4.0.0",
"clone": "^2.1.2",
"debug": "^4.1.1",
"fetch-retry": "^3.1.0",
@@ -50,6 +51,7 @@
"livereload": "^0.9.1",
"manakin": "^0.5.2",
"openwhisk": "^3.21.1",
+ "ora": "^4.0.3",
"pretty-bytes": "^5.3.0",
"pretty-ms": "^6.0.1",
"yargs": "^15.3.1"
diff --git a/src/agentmgr.js b/src/agentmgr.js
index f96c6fa..3905847 100644
--- a/src/agentmgr.js
+++ b/src/agentmgr.js
@@ -27,8 +27,8 @@
const fs = require('fs-extra');
const sleep = require('util').promisify(setTimeout);
-const debug = require('./debug');
const clone = require('clone');
+const log = require('./log');
function getAnnotation(action, key) {
const a = action.annotations.find(a => a.key === key);
@@ -78,7 +78,7 @@
if (await actionExists(wsk, name)) {
await wsk.actions.delete(name);
}
- debug(`restore: ensured removal of action ${name}`);
+ log.debug(`restore: ensured removal of action ${name}`);
}
@@ -99,9 +99,6 @@
* Fast way to get just the action metadata
*/
async peekAction() {
- if (this.argv.verbose) {
- console.log(`Getting action metadata from OpenWhisk: ${this.actionName}`);
- }
let action = await getWskActionWithoutCode(this.wsk, this.actionName);
if (action === null) {
throw new Error(`Action not found: ${this.actionName}`);
@@ -126,7 +123,7 @@
throw new Error(`Dang! Agent is already installed and action backup is broken (${backupName}).\n\nPlease redeploy your action first before running wskdebug again.`);
} else {
- console.warn("Agent was already installed, but backup is still present. All good.");
+ log.warn("Agent was already installed, but backup is still present. All good.");
// need to look at the original action
action = backup;
@@ -148,14 +145,10 @@
}
async readActionWithCode() {
- if (this.argv.verbose) {
- console.log(`Fetching action code from OpenWhisk: ${this.actionName}`);
- }
-
// user can switch between agents (ngrok or not), hence we need to restore first
// (better would be to track the agent + its version and avoid a restore, but that's TBD)
if (this.agentInstalled) {
- this.actionWithCode = await this.restoreAction();
+ this.actionWithCode = await this.restoreAction(true);
} else {
this.actionWithCode = await this.wsk.actions.get(this.actionName);
}
@@ -167,7 +160,7 @@
return this.actionWithCode;
}
- async installAgent(invoker, debugTask) {
+ async installAgent(invoker, debug2) {
this.agentInstalled = true;
let agentName;
@@ -189,15 +182,17 @@
// agent using ngrok for forwarding
agentName = "ngrok";
agentCode = await this.ngrokAgent.getAgent(agentAction);
- debugTask("started local ngrok proxy");
+ debug2("started local ngrok proxy");
} else {
if (this.argv.disableConcurrency) {
this.concurrency = false;
+
} else {
this.concurrency = await this.openwhiskSupports("concurrency");
+ debug2(`fetched openwhisk /api/v1/api-docs to detect concurrency`);
if (!this.concurrency) {
- console.warn("This OpenWhisk does not support action concurrency. Debugging will be a bit slower. Consider using '--ngrok' which might be a faster option.");
+ log.warn("This OpenWhisk does not support action concurrency. Debugging will be a bit slower. Consider using '--ngrok' which might be a faster option.");
}
}
@@ -214,26 +209,18 @@
const backupName = getActionCopyName(this.actionName);
- if (this.argv.verbose) {
- console.log(`Installing agent in OpenWhisk (${agentName})...`);
- }
-
// create copy in case wskdebug gets killed hard
// do async as this can be slow for larger actions and this is part of the critical startup path
this.createBackup = (async () => {
- const debugTask = debug.task();
+ const debug3 = log.newDebug();
await this.wsk.actions.update({
name: backupName,
action: agentAction
});
- debugTask(`created action backup ${backupName}`);
+ debug3(`created action backup ${backupName}`);
})();
- if (this.argv.verbose) {
- console.log(`Original action will be backed up at ${backupName}.`);
- }
-
if (this.argv.condition) {
agentAction.parameters.push({
key: "$condition",
@@ -246,17 +233,13 @@
} catch (e) {
// openwhisk does not support concurrent nodejs actions, try with another
if (e.statusCode === 400 && e.error && typeof e.error.error === "string" && e.error.error.includes("concurrency")) {
- console.log(`The Openwhisk server does not support concurrent actions, using alternative agent. Consider using --ngrok for a possibly faster agent.`);
+ log.log(`The Openwhisk server does not support concurrent actions, using alternative agent. Consider using --ngrok for a possibly faster agent.`);
this.concurrency = false;
agentCode = await this.getPollingActivationDbAgent();
await this.pushAgent(agentAction, agentCode, backupName);
}
}
- debugTask(`installed agent '${agentName}' in place of ${this.actionName}`);
-
- if (this.argv.verbose) {
- console.log(`Agent installed.`);
- }
+ debug2(`installed agent type '${agentName}' in place of action '${this.actionName}'`);
}
stop() {
@@ -276,7 +259,7 @@
} finally {
if (this.ngrokAgent) {
await this.ngrokAgent.stop();
- debug("ngrok shut down");
+ log.debug("ngrok shut down");
}
}
}
@@ -302,9 +285,8 @@
blocking: true
});
- if (this.argv.verbose) {
- process.stdout.write(".");
- }
+ log.verboseWrite(".");
+
} else {
// poll for the newest activation
const since = Date.now();
@@ -339,18 +321,14 @@
}
}
- if (this.argv.verbose) {
- process.stdout.write(".");
- }
+ log.verboseWrite(".");
// need to limit load on openwhisk (activation list)
await sleep(1000);
}
}
- if (this.argv.verbose) {
- process.stdout.write(".");
- }
+ log.verboseWrite(".");
// check for successful response with a new activation
if (activation && activation.response) {
@@ -359,13 +337,9 @@
// mark this as seen so we don't reinvoke it
this.activationsSeen[activation.activationId] = true;
- if (this.argv.verbose) {
- console.log();
- console.info(`Activation: ${params.$activationId}`);
- console.log(params);
- } else {
- console.info(`Activation: ${params.$activationId}`);
- }
+ log.verbose(); // because of the .....
+ log.log();
+ log.highlight("Activation: ", params.$activationId);
return params;
} else if (activation && activation.activationId) {
@@ -377,7 +351,7 @@
} else {
// unexpected, just log and retry
- console.log("Unexpected empty response while waiting for new activations:", activation);
+ log.log("Unexpected empty response while waiting for new activations:", activation);
}
} catch (e) {
@@ -385,30 +359,26 @@
const errorCode = getActivationError(e).code;
if (errorCode === 42) {
// 42 => retry, do nothing here (except logging progress)
- if (this.argv.verbose) {
- process.stdout.write(".");
- }
+ log.verboseWrite(".");
} else if (errorCode === 43) {
// 43 => graceful shutdown (for unit tests)
- console.log("Graceful shutdown requested by agent (only for unit tests)");
+ log.log("Graceful shutdown requested by agent (only for unit tests)");
return null;
} else if (e.statusCode === 503 && !this.concurrency) {
// 503 => openwhisk activation DB likely overloaded with requests, warn, wait a bit and retry
- if (this.argv.verbose) {
- console.log("x");
- }
- console.warn("Server responded with 503 while looking for new activation records. Consider using --ngrok option.")
+ log.verbose("x");
+ log.warn("Server responded with 503 while looking for new activation records. Consider using --ngrok option.")
await sleep(5000);
} else {
// otherwise log error and abort
- console.error();
- console.error("Unexpected error while polling agent for activation:");
- console.dir(e, { depth: null });
+ log.error();
+ log.error("Unexpected error while polling agent for activation:");
+ log.deepObject(e);
throw new Error("Unexpected error while polling agent for activation.");
}
}
@@ -419,10 +389,8 @@
}
async completeActivation(activationId, result, duration) {
- console.info(`Completed activation ${activationId} in ${duration/1000.0} sec`);
- if (this.argv.verbose) {
- console.log(result);
- }
+ log.succeed(`Completed activation ${activationId} in ` + log.highlightColor(`${duration/1000.0} sec`));
+ log.verbose("Result:", result);
try {
result.$activationId = activationId;
@@ -439,10 +407,10 @@
// do nothing
} else if (errorCode === 43) {
// 43 => graceful shutdown (for unit tests)
- console.log("Graceful shutdown requested by agent (only for unit tests)");
+ log.log("Graceful shutdown requested by agent (only for unit tests)");
return false;
} else {
- console.error("Unexpected error while completing activation:", e);
+ log.error("Unexpected error while completing activation:", e);
}
}
return true;
@@ -450,12 +418,7 @@
// --------------------------------------< restoring >------------------
- async restoreAction() {
- if (this.argv.verbose) {
- console.log();
- console.log(`Restoring action`);
- }
-
+ async restoreAction(isStartup) {
const copy = getActionCopyName(this.actionName);
try {
@@ -470,7 +433,7 @@
} else {
// the original was fetched before or was backed up in the copy
original = await this.wsk.actions.get(copy)
- debug("restore: fetched action original from backup copy");
+ log.debug("restore: fetched action original from backup copy");
}
// copy the backup (copy) to the regular action
@@ -478,31 +441,34 @@
name: this.actionName,
action: original
});
- debug("restore: restored original action");
+ log.debug("restore: restored original action");
if (this.argv.cleanup) {
- console.log("Removing extra actions due to --cleanup...");
+ if (!isStartup) {
+ log.log("Removing helper actions due to --cleanup...");
+ }
// remove the backup
await this.wsk.actions.delete(copy);
- debug("restore: deleted backup copy");
+ log.debug("restore: deleted backup copy");
// remove any helpers if they exist
await deleteActionIfExists(this.wsk, `${this.actionName}_wskdebug_invoked`);
await deleteActionIfExists(this.wsk, `${this.actionName}_wskdebug_completed`);
- } else {
- console.warn(`Skipping removal of extra actions. Remove using --cleanup if desired:`);
- console.warn(`- ${copy}`);
- if (!this.concurrency) {
- console.warn(`- ${this.actionName}_wskdebug_invoked`);
- console.warn(`- ${this.actionName}_wskdebug_completed`);
+ } else if (!isStartup) {
+ log.log(`Following helper actions are not removed to make shutdown fast. Remove using --cleanup if desired.`);
+ log.log(`- ${log.highlightColor(copy)}`);
+ if (!this.concurrency && !this.ngrokAgent) {
+ log.log("- " + log.highlightColor(`${this.actionName}_wskdebug_invoked`));
+ log.log("- " + log.highlightColor(`${this.actionName}_wskdebug_completed`));
}
+ log.log();
}
return original;
} catch (e) {
- console.error("Error while restoring original action:", e);
+ log.error("Error while restoring original action:", e);
}
}
@@ -554,7 +520,7 @@
{ key: "wskdebug", value: true },
{ key: "description", value: `wskdebug agent. temporarily installed over original action. original action backup at ${backupName}.` }
],
- parameters: action.parameters
+ parameters: action.parameters || []
}
});
}
@@ -578,7 +544,7 @@
]
}
});
- debug(`created helper action ${actionName}`);
+ log.debug(`created helper action ${actionName}`);
}
// ----------------------------------------< openwhisk feature detection >-----------------
@@ -593,7 +559,7 @@
this.openwhiskVersion = null;
}
} catch (e) {
- console.warn("Could not retrieve OpenWhisk version:", e.message);
+ log.warn("Could not retrieve OpenWhisk version:", e.message);
this.openwhiskVersion = null;
}
}
@@ -615,7 +581,7 @@
return swagger.definitions.ActionLimits.properties.concurrency;
}
} catch (e) {
- console.warn('Could not read /api/v1/api-docs, setting max action concurrency to 1')
+ log.warn('Could not read /api/v1/api-docs, setting max action concurrency to 1')
return false;
}
}
diff --git a/src/agents/ngrok.js b/src/agents/ngrok.js
index 1ee2a45..9a1b420 100644
--- a/src/agents/ngrok.js
+++ b/src/agents/ngrok.js
@@ -23,6 +23,7 @@
const url = require('url');
const util = require('util');
const crypto = require("crypto");
+const log = require('../log');
class NgrokAgent {
constructor(argv, invoker) {
@@ -31,9 +32,7 @@
}
async getAgent(action) {
- if (this.argv.verbose) {
- console.log("Setting up ngrok", this.argv.ngrokRegion ? `(region: ${this.argv.ngrokRegion})` : "");
- }
+ log.verbose("Setting up ngrok", this.argv.ngrokRegion ? `(region: ${this.argv.ngrokRegion})` : "");
// 1. start local server on random port
this.ngrokServer = http.createServer(this.ngrokHandler.bind(this));
@@ -62,7 +61,9 @@
value: this.ngrokAuth
});
- console.log(`Ngrok forwarding: ${ngrokUrl} => http://localhost:${this.ngrokServerPort} (auth: ${this.ngrokAuth})`);
+ const h = log.highlightColor;
+ log.step(`Ngrok forwarding: ${h(ngrokUrl)} => http://localhost:${h(this.ngrokServerPort)}`);
+ log.debug(`ngrok agent auth key: ${this.ngrokAuth}`)
return fs.readFileSync(`${__dirname}/../../agent/agent-ngrok.js`, {encoding: 'utf8'});
}
@@ -99,33 +100,28 @@
req.on('end', async () => {
try {
const params = JSON.parse(body);
- const id = params.$activationId;
+ const activationId = params.$activationId;
delete params.$activationId;
- if (this.argv.verbose) {
- console.log();
- console.info(`Activation: ${id}`);
- console.log(params);
- } else {
- console.info(`Activation: ${id}`);
- }
+ log.verbose(); // because of the .....
+ log.log();
+ log.highlight("Activation: ", activationId);
+ log.verbose("Parameters:", params);
const startTime = Date.now();
- const result = await this.invoker.run(params, id);
+ const result = await this.invoker.run(params, activationId);
const duration = Date.now() - startTime;
- console.info(`Completed activation ${id} in ${duration/1000.0} sec`);
- if (this.argv.verbose) {
- console.log(result);
- }
+ log.succeed(`Completed activation ${activationId} in ` + log.highlightColor(`${duration/1000.0} sec`));
+ log.verbose("Result:", result);
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(result));
} catch (e) {
- console.error(e);
+ log.error(e);
res.statusCode = 400;
res.end();
}
diff --git a/src/debug.js b/src/debug.js
deleted file mode 100644
index 17ac7b9..0000000
--- a/src/debug.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-'use strict';
-
-// common debug() instance for shared time spent measurments (+millis)
-module.exports = require('debug')('wskdebug');
-
-// start a sub debug instance for logging times in parallel promises
-module.exports.task = () => {
- const debug = require('debug')('wskdebug')
- // trick to start time measurement from now on without logging an extra line
- debug.log = () => {};
- debug();
- delete debug.log;
- return debug;
-}
diff --git a/src/debugger.js b/src/debugger.js
index 879d497..e7d250b 100644
--- a/src/debugger.js
+++ b/src/debugger.js
@@ -24,9 +24,17 @@
const openwhisk = require('openwhisk');
const { spawnSync } = require('child_process');
const sleep = require('util').promisify(setTimeout);
-const debug = require('./debug');
const prettyBytes = require('pretty-bytes');
const prettyMilliseconds = require('pretty-ms');
+const log = require('./log');
+
+function prettyMBytes1024(mb) {
+ if (mb > 1024) {
+ return `${mb/1024} GB`;
+ } else {
+ return `${mb} MB`;
+ }
+}
/**
* Central component of wskdebug.
@@ -34,14 +42,14 @@
class Debugger {
constructor(argv) {
this.startTime = Date.now();
- debug("starting debugger");
+ log.debug("starting debugger");
this.argv = argv;
this.actionName = argv.action;
this.wskProps = wskprops.get();
if (Object.keys(this.wskProps).length === 0) {
- console.error(`Error: Missing openwhisk credentials. Found no ~/.wskprops file or WSK_* environment variable.`);
+ log.error(`Error: Missing openwhisk credentials. Found no ~/.wskprops file or WSK_* environment variable.`);
process.exit(1);
}
if (argv.ignoreCerts) {
@@ -51,9 +59,12 @@
try {
this.wsk = openwhisk(this.wskProps);
} catch (err) {
- console.error(`Error: Could not setup openwhisk client: ${err.message}`);
+ log.error(`Error: Could not setup openwhisk client: ${err.message}`);
process.exit(1);
}
+
+ const h = log.highlightColor;
+ log.spinner("Debugging " + h(`/_/${this.actionName}`) + " on " + h(this.wskProps.apihost));
}
async start() {
@@ -61,19 +72,20 @@
this.watcher = new Watcher(this.argv, this.wsk);
// get the action metadata
- const actionMetadata = await this.agentMgr.peekAction();
- debug("fetched action metadata from openwhisk");
+ this.actionMetadata = await this.agentMgr.peekAction();
+ log.debug("fetched action metadata from openwhisk");
+ this.wskProps.namespace = this.actionMetadata.namespace;
- this.wskProps.namespace = actionMetadata.namespace;
- console.info(`Starting debugger for /${this.wskProps.namespace}/${this.actionName}`);
+ const h = log.highlightColor;
+ log.step("Debugging " + h(`/${this.wskProps.namespace}/${this.actionName}`) + " on " + h(this.wskProps.apihost));
// local debug container
- this.invoker = new OpenWhiskInvoker(this.actionName, actionMetadata, this.argv, this.wskProps, this.wsk);
+ this.invoker = new OpenWhiskInvoker(this.actionName, this.actionMetadata, this.argv, this.wskProps, this.wsk);
try {
// run build initially (would be required by starting container)
if (this.argv.onBuild) {
- console.info("=> Build:", this.argv.onBuild);
+ log.highlight("On build: ", this.argv.onBuild);
spawnSync(this.argv.onBuild, {shell: true, stdio: "inherit"});
}
await this.invoker.prepare();
@@ -82,20 +94,21 @@
// task 1 - start local container
const containerTask = (async () => {
- const debugTask = debug.task();
+ const debug2 = log.newDebug();
+ log.spinner('Starting local container');
// start container - get it up fast for VSCode to connect within its 10 seconds timeout
- await this.invoker.startContainer();
+ await this.invoker.startContainer(debug2);
- debugTask(`started container: ${this.invoker.name()}`);
+ debug2(`started container: ${this.invoker.name()}`);
})();
// task 2 - fetch action code from openwhisk
const openwhiskTask = (async () => {
- const debugTask = debug.task();
+ const debug2 = log.newDebug();
const actionWithCode = await this.agentMgr.readActionWithCode();
- debugTask(`got action code (${prettyBytes(actionWithCode.exec.code.length)})`);
+ debug2(`downloaded action code (${prettyBytes(actionWithCode.exec.code.length)})`);
return actionWithCode;
})();
@@ -103,51 +116,41 @@
const results = await Promise.all([containerTask, openwhiskTask]);
const actionWithCode = results[1];
+ log.spinner('Installing agent');
+
// parallelize slower work using promises again
// task 3 - initialize local container with code
const initTask = (async () => {
- const debugTask = debug.task();
+ const debug2 = log.newDebug();
// /init local container
await this.invoker.init(actionWithCode);
- debugTask("installed action on container");
+ debug2("installed action on container");
})();
// task 4 - install agent in openwhisk
const agentTask = (async () => {
- const debugTask = debug.task();
+ const debug2 = log.newDebug();
// setup agent in openwhisk
- await this.agentMgr.installAgent(this.invoker, debugTask);
+ await this.agentMgr.installAgent(this.invoker, debug2);
})();
await Promise.all([initTask, agentTask]);
if (this.argv.onStart) {
- console.log("On start:", this.argv.onStart);
+ log.highlight("On start: ", this.argv.onStart);
spawnSync(this.argv.onStart, {shell: true, stdio: "inherit"});
}
// start source watching (live reload) if requested
await this.watcher.start();
- console.log();
- console.info(`Action : /${this.wskProps.namespace}/${this.actionName}`);
- if (this.sourcePath) {
- console.info(`Sources : ${this.invoker.getSourcePath()}`);
- }
- console.info(`Image : ${this.invoker.getImage()}`);
- console.info(`OpenWhisk : ${this.wskProps.apihost}`);
- console.info(`Container : ${this.invoker.name()}`);
- console.info(`Debug type : ${this.invoker.getDebugKind()}`);
- console.info(`Debug port : localhost:${this.invoker.getPort()}`);
- if (this.argv.condition) {
- console.info(`Condition : ${this.argv.condition}`);
- }
- console.log();
- console.info(`Ready for activations. Started in ${prettyMilliseconds(Date.now() - this.startTime)}. Use CTRL+C to exit`);
+ this.logDetails();
+ const abortMsg = log.isInteractive ? log.highlightColor(" Use CTRL+C to exit.") : "";
+ log.ready(`Ready for activations. Started in ${prettyMilliseconds(Date.now() - this.startTime)}.${abortMsg}`);
this.ready = true;
@@ -157,6 +160,30 @@
}
}
+ async logDetails() {
+ log.log();
+ log.highlight("Action : ", `/${this.wskProps.namespace}/${this.actionName}`);
+ if (this.sourcePath) {
+ log.highlight("Sources : ", `${this.invoker.getSourcePath()}`);
+ }
+ log.highlight("Image : ", `${this.invoker.getImage()}`);
+ log.highlight("Container : ", `${this.invoker.name()}`);
+ if (this.actionMetadata.limits) {
+ if (this.actionMetadata.limits.memory) {
+ log.highlight("Memory : ", `${prettyMBytes1024(this.actionMetadata.limits.memory)}`);
+ }
+ if (this.actionMetadata.limits.timeout) {
+ log.highlight("Timeout : ", `${prettyMilliseconds(this.actionMetadata.limits.timeout, {verbose:true})}`);
+ }
+ }
+ log.highlight("Debug type : ", `${this.invoker.getDebugKind()}`);
+ log.highlight("Debug port : ", `localhost:${this.invoker.getPort()}`);
+ if (this.argv.condition) {
+ log.highlight("Condition : ", `${this.argv.condition}`);
+ }
+ log.log();
+ }
+
async run() {
return this.runPromise = this._run();
}
@@ -164,7 +191,6 @@
async _run() {
try {
this.running = true;
- this.shuttingDown = false;
// main blocking loop
// abort if this.running is set to false
@@ -186,6 +212,7 @@
const id = activation.$activationId;
delete activation.$activationId;
+ log.verbose("Parameters:", activation);
const startTime = Date.now();
@@ -234,18 +261,25 @@
async shutdown() {
// avoid duplicate shutdown on CTRL+C
- if (this.shuttingDown) {
- return;
+ if (!this.shutdownPromise) {
+ this.shutdownPromise = this._shutdown();
}
- this.shuttingDown = true;
+
+ await this.shutdownPromise;
+ delete this.shutdownPromise;
+ }
+
+ async _shutdown() {
const shutdownStart = Date.now();
- debug("shutting down...");
// only log this if we started properly
if (this.ready) {
- console.log();
- console.log();
- console.log("Shutting down...");
+ log.log();
+ log.log();
+ log.debug("shutting down...");
+ log.spinner("Shutting down");
+ } else {
+ log.debug("aborting start - shutting down ...");
}
// need to shutdown everything even if some fail, hence tryCatch() for each
@@ -255,23 +289,23 @@
}
if (this.invoker) {
await this.tryCatch(this.invoker.stop());
- debug(`stopped container: ${this.invoker.name()}`);
+ log.debug(`stopped container: ${this.invoker.name()}`);
}
if (this.watcher) {
await this.tryCatch(this.watcher.stop());
- debug("stopped source file watching");
+ log.debug("stopped source file watching");
}
// only log this if we started properly
if (this.ready) {
- console.log(`Done (shutdown took ${prettyMilliseconds(Date.now() - shutdownStart)})`);
+ log.succeed(`Done. Shutdown in ${prettyMilliseconds(Date.now() - shutdownStart)}.`);
}
this.ready = false;
}
// ------------------------------------------------< utils >-----------------
- async tryCatch(task, message="Error during shutdown:") {
+ async tryCatch(task) {
try {
if (typeof task === "function") {
task();
@@ -279,13 +313,7 @@
await task;
}
} catch (e) {
- console.log(e);
- if (this.argv.verbose) {
- console.error(message);
- console.error(e);
- } else {
- console.error(message, e.message);
- }
+ log.exception(e, "Error during shutdown:");
}
}
diff --git a/src/invoker.js b/src/invoker.js
index eed849a..32b9d65 100644
--- a/src/invoker.js
+++ b/src/invoker.js
@@ -21,6 +21,7 @@
const fetch = require('fetch-retry')(require('isomorphic-fetch'));
const kinds = require('./kinds/kinds');
const path = require('path');
+const log = require("./log");
const RUNTIME_PORT = 8080;
const INIT_RETRY_DELAY_MS = 100;
@@ -31,12 +32,11 @@
memory: 256
};
-function execute(cmd, options, verbose) {
+function execute(cmd, options, debug2) {
cmd = cmd.replace(/\s+/g, ' ');
- if (verbose) {
- console.log(cmd);
- }
const result = execSync(cmd, options);
+
+ (debug2 || log.debug)(`executed: ${cmd}`);
if (result) {
return result.toString().trim();
} else {
@@ -65,7 +65,6 @@
this.internalPort = options.internalPort;
this.command = options.command;
this.dockerArgs = options.dockerArgs;
- this.verbose = options.verbose;
// the build path can be separate, if not, same as the source/watch path
this.sourcePath = options.buildPath || options.sourcePath;
@@ -107,14 +106,12 @@
}
return runtimes[kind];
- } else if (this.verbose) {
- console.warn("Could not retrieve runtime images from OpenWhisk, using default image list.");
+ } else {
+ log.warn("Could not retrieve runtime images from OpenWhisk, using default image list.");
}
} catch (e) {
- if (this.verbose) {
- console.warn("Could not retrieve runtime images from OpenWhisk, using default image list.", e.message);
- }
+ log.warn("Could not retrieve runtime images from OpenWhisk, using default image list.", e.message);
}
return kinds.images[kind];
}
@@ -146,9 +143,7 @@
try {
this.debug = require(`${__dirname}/kinds/${this.debugKind}/${this.debugKind}`);
} catch (e) {
- if (this.verbose) {
- console.error(`Cannot find debug info for kind ${this.debugKind}:`, e.message);
- }
+ log.warn(`Cannot find debug info for kind ${this.debugKind}:`, e.message);
this.debug = {};
}
@@ -175,7 +170,7 @@
// source mounting
if (this.sourcePath) {
if (!this.debug.mountAction) {
- console.warn(`Warning: Sorry, mounting sources not yet supported for: ${kind}.`);
+ log.warn(`Warning: Sorry, mounting sources not yet supported for: ${kind}.`);
this.sourcePath = undefined;
}
}
@@ -188,8 +183,8 @@
}
}
- async startContainer() {
- let showDockerRunOutput = this.verbose;
+ async startContainer(debug2) {
+ let showDockerRunOutput = log.isVerbose;
// quick fail for missing requirements such as docker not running
await this.checkIfDockerAvailable();
@@ -199,7 +194,7 @@
} catch (e) {
// make sure the user can see the image download process as part of docker run
showDockerRunOutput = true;
- console.log(`
+ log.warn(`
+------------------------------------------------------------------------------------------+
| Docker image must be downloaded: ${this.image}
| |
@@ -213,10 +208,6 @@
`);
}
- if (this.verbose) {
- console.log(`Starting local debug container ${this.name()}`);
- }
-
execute(
`docker run
-d
@@ -232,11 +223,12 @@
`,
// live stream view for docker image download output
{ stdio: showDockerRunOutput ? "inherit" : null },
- this.verbose
+ debug2
);
this.containerRunning = true;
+ log.stopSpinner();
spawn("docker", ["logs", "-t", "-f", this.name()], {
stdio: [
"inherit", // stdin
@@ -265,16 +257,9 @@
async init(actionWithCode) {
let action;
if (this.sourceMountAction) {
- if (this.verbose) {
- console.log(`Mounting sources onto local debug container: ${this.sourcePath}`);
- }
-
action = this.sourceMountAction;
} else {
- if (this.verbose) {
- console.log(`Pushing action code to local debug container: ${this.action.name}`);
- }
action = {
binary: actionWithCode.exec.binary,
main: actionWithCode.exec.main || "main",
@@ -324,9 +309,6 @@
async stop() {
if (this.containerRunning) {
- if (this.verbose) {
- console.log("Stopping local debug container");
- }
execute(`docker kill ${this.name()}`);
}
}
diff --git a/src/log.js b/src/log.js
new file mode 100644
index 0000000..fc39e25
--- /dev/null
+++ b/src/log.js
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const dbg = require('debug');
+const ora = require('ora');
+const chalk = require('chalk');
+
+const INFO_COLOR_ANSI = 36;
+const infoColor = chalk.ansi(INFO_COLOR_ANSI);
+const highlightColor = chalk.magenta;
+
+const spinner = ora({
+ color: "cyan",
+ stream: process.stdout
+});
+
+const DEBUG_NAMESPACE = "wskdebug"
+const debug = dbg(DEBUG_NAMESPACE);
+
+const noop = () => {};
+
+// no emoji support in windows terminal
+const useEmoji = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color';
+const symbols = useEmoji ? {
+ step: '❯',
+ success: '✔',
+ ready: '🚀'
+} : {
+ step: '-',
+ success: '√',
+ ready: '>'
+};
+
+if (debug.enabled || !spinner.isEnabled) {
+ // disable spinner since we have all the debug() logs saying similar stuff
+ spinner.start = noop;
+ spinner.stop = noop;
+}
+
+let originalConsole = null;
+
+module.exports = {
+
+ isVerbose: false,
+
+ silent: function(silent) {
+ if (silent) {
+ // silent wins
+ this.isVerbose = false;
+ dbg.disable();
+
+ this.log = noop;
+ this.step = noop;
+ this.highlight = noop;
+ this.warn = noop;
+ this.verboseWrite = noop;
+ this.deepObject = noop;
+ spinner.start = noop;
+ spinner.stopAndPersist = noop;
+ }
+ },
+
+ /** Important step message, prefixed with symbol, visible by default. Ends any running spinner(). */
+ step: function(text) {
+ spinner.stopAndPersist({
+ symbol: infoColor(symbols.step),
+ text: spinner.isEnabled ? infoColor(text) : text
+ });
+ },
+
+ highlight: function(text, highlight) {
+ this.step(text + highlightColor(highlight));
+ },
+
+ highlightColor,
+
+ /** Basic log message, visible by default. Ends any running spinner(). */
+ log: function(...args) {
+ spinner.stop();
+ // goes to stdout
+ console.info(...args);
+ },
+
+ /** Warning message, visible by default. Ends any running spinner(). */
+ warn: function(...args) {
+ spinner.stop();
+ // goes to stderr
+ console.warn(...args);
+ },
+
+ /** Error message, visible by default. Ends any running spinner(). */
+ error: function(...args) {
+ spinner.stop();
+ // goes to stderr
+ console.error(...args);
+ },
+
+ verbose: function(...args) {
+ if (this.isVerbose) {
+ this.log(...args);
+ }
+ },
+
+ verboseWrite: function(text) {
+ if (this.isVerbose) {
+ process.stdout.write(text);
+ }
+ },
+
+ exception: function(err, message="Error:") {
+ // stacktrace only in verbose
+ if (this.isVerbose) {
+ this.error(err);
+ } else {
+ this.error(message, err.message);
+ }
+ },
+
+ deepObject: function(obj) {
+ console.dir(obj, { depth: null });
+ },
+
+ // common debug() instance for shared time spent measurments (+millis)
+ debug,
+
+ /**
+ * Create a new "child" debug instance for logging times in parallel promises
+ */
+ newDebug: function() {
+ const debug = dbg(DEBUG_NAMESPACE);
+ // trick to start time measurement from now on without logging an extra line
+ debug.log = () => {};
+ debug();
+ delete debug.log;
+ return debug;
+ },
+
+ /** Start a spinner on the console */
+ spinner: function(text) {
+ spinner.start(infoColor(text) + " ");
+ },
+
+ /** Stop a running spinner(). */
+ stopSpinner: function() {
+ spinner.stop();
+ },
+
+ /** Finish any running spinner and show a log message with a success symbol in front. */
+ succeed: function(text) {
+ spinner.stopAndPersist({
+ symbol: chalk.green(symbols.success),
+ text: infoColor(text)
+ });
+ },
+
+ /** Finish any running spinner and show a log message with a ready symbol in front. */
+ ready: function(text) {
+ spinner.stopAndPersist({
+ symbol: infoColor(symbols.ready),
+ text: infoColor(text)
+ });
+ },
+
+ enableConsoleColors: function() {
+ // colorful console.log() and co
+ if (!console._logToFile) {
+ originalConsole = {
+ log: console.log,
+ error: console.error,
+ info: console.info,
+ debug: console.debug
+ };
+ // overwrites console.*()
+ const manakin = require('manakin').global;
+ manakin.info.color = INFO_COLOR_ANSI;
+
+ // no bright as it might not look good on terminals with white background
+ //manakin.setBright();
+ }
+ return originalConsole;
+ },
+
+ resetConsoleColors: function() {
+ if (originalConsole) {
+ console.log = originalConsole.log;
+ console.error = originalConsole.error;
+ console.info = originalConsole.info;
+ console.debug = originalConsole.debug;
+ }
+ },
+
+ isInteractive: spinner.isEnabled
+};
+
+if (process.env.WSKDEBUG_SILENT) {
+ module.exports.silent(true);
+}
diff --git a/src/watcher.js b/src/watcher.js
index dcea5a6..45558b5 100644
--- a/src/watcher.js
+++ b/src/watcher.js
@@ -20,7 +20,7 @@
const fs = require('fs-extra');
const livereload = require('livereload');
const { spawnSync } = require('child_process');
-const debug = require('./debug');
+const log = require('./log');
class Watcher {
constructor(argv, wsk) {
@@ -38,6 +38,8 @@
|| this.argv.invokeParams
|| this.argv.invokeAction )
) {
+ log.spinner('Initializing source watching');
+
this.liveReloadServer = livereload.createServer({
port: this.argv.livereloadPort,
noListen: !this.argv.livereload,
@@ -55,9 +57,7 @@
try {
let result = [];
- if (argv.verbose) {
- console.log("File modified:", filepath);
- }
+ log.verbose("File modified:", filepath);
// call original function if we are listening
if (argv.livereload) {
@@ -66,13 +66,13 @@
// run build command before invoke triggers below
if (argv.onBuild) {
- console.info("=> Build:", argv.onBuild);
+ log.highlight("On build: ", argv.onBuild);
spawnSync(argv.onBuild, {shell: true, stdio: "inherit"});
}
// run shell command
if (argv.onChange) {
- console.info("=> Run:", argv.onChange);
+ log.highlight("On run: ", argv.onChange);
spawnSync(argv.onChange, {shell: true, stdio: "inherit"});
}
@@ -91,22 +91,22 @@
name: action,
params: json
}).then(response => {
- console.info(`=> Invoked action ${action} with params ${argv.invokeParams}: ${response.activationId}`);
+ log.step(`Invoked action ${action} with params ${argv.invokeParams}: ${response.activationId}`);
}).catch(err => {
- console.error("Error invoking action:", err);
+ log.error("Error invoking action:", err);
});
}
return result;
} catch (e) {
- console.error(e);
+ log.error(e);
}
};
if (this.argv.livereload) {
- console.info(`LiveReload enabled for ${watch} on port ${this.liveReloadServer.config.port}`);
+ log.log(`LiveReload enabled for ${log.highlightColor(watch)} on port ${this.liveReloadServer.config.port}`);
}
- debug("started source file watching");
+ log.debug("started source file watching");
}
}
diff --git a/test/test.js b/test/test.js
index d81bee1..0aaeed6 100644
--- a/test/test.js
+++ b/test/test.js
@@ -42,9 +42,7 @@
async function beforeEach() {
process.env.WSK_CONFIG_FILE = path.join(process.cwd(), "test/wskprops");
- // nock.recorder.rec({ enable_reqheaders_recording: true });
openwhisk = nock(FAKE_OPENWHISK_SERVER);
- // openwhisk.log(console.log);
mockOpenwhiskSwagger(openwhisk);
// save current working dir