From f281658c90b860ec9471aebd957f6e041c93fa22 Mon Sep 17 00:00:00 2001 From: Tom Meyer Date: Wed, 19 May 2021 16:12:05 -0400 Subject: [PATCH 01/13] Do not run the `babel` helper in Development Mode. (#224) (#225) This PR adds a check to the `babel` helper to see if we are currently in the Development Mode. If so, the enclosed source code is not run through Babel, it is returned verbatim. This is a preview optimization, since the Babel-ification is a relatively slow operation. It's not necessary for preview either, assuming devs are not previewing their changes in IE11. J=SLAP-1312 TEST=manual Ensured that when in Development mode, the helper was a simple pass-through. In Production mode, the enclosed code was Babel-ified. --- src/handlebars/registerhbshelpers.js | 30 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/handlebars/registerhbshelpers.js b/src/handlebars/registerhbshelpers.js index b9c936dd..37aa80ce 100644 --- a/src/handlebars/registerhbshelpers.js +++ b/src/handlebars/registerhbshelpers.js @@ -79,19 +79,25 @@ module.exports = function registerHbsHelpers(hbs) { hbs.registerHelper('babel', function(options) { const srcCode = options.fn(this); - return babel.transformSync(srcCode, { - compact: true, - minified: true, - sourceType: 'script', - presets: ['@babel/preset-env'], - plugins: [ - '@babel/syntax-dynamic-import', - '@babel/plugin-transform-arrow-functions', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-transform-object-assign', - ] + + if (process.env.IS_DEVELOPMENT_PREVIEW === 'true' ) { + return srcCode; + } else { + return babel.transformSync(srcCode, { + compact: true, + minified: true, + comments: false, + sourceType: 'script', + presets: ['@babel/preset-env'], + plugins: [ + '@babel/syntax-dynamic-import', + '@babel/plugin-transform-arrow-functions', + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-transform-object-assign', + ] }).code; - }) + } + }); hbs.registerHelper('partialPattern', function(cardPath, opt) { let result = ''; From df84073f93764e45dcb1a2374cf4eea5d57e23b3 Mon Sep 17 00:00:00 2001 From: Tom Meyer Date: Thu, 27 May 2021 10:24:36 -0400 Subject: [PATCH 02/13] Remove outdated `sample_site` from Jambo. (#226) (#227) This PR removes the sample site that was included with Jambo. This sample is very much out of date. It was created at Jambo's inception. The Hitchhiker Training and Theme provide much better guidance on how to use Jambo. J=SLAP-1239 TEST=none --- sample_site/config.json | 2 -- sample_site/config/global_config.json | 1 - sample_site/config/index.json | 1 - sample_site/overrides/placeholder.hbs | 1 - sample_site/package-lock.json | 5 ----- sample_site/package.json | 11 ----------- sample_site/pages/index.html.hbs | 1 - sample_site/theme/cream/standard/config.json | 0 sample_site/theme/cream/standard/markup/searchbar.hbs | 0 sample_site/theme/cream/standard/page.html.hbs | 9 --------- sample_site/theme/cream/standard/script/core.hbs | 0 sample_site/theme/cream/standard/script/searchbar.hbs | 0 12 files changed, 31 deletions(-) delete mode 100644 sample_site/config.json delete mode 100644 sample_site/config/global_config.json delete mode 100644 sample_site/config/index.json delete mode 100644 sample_site/overrides/placeholder.hbs delete mode 100644 sample_site/package-lock.json delete mode 100644 sample_site/package.json delete mode 100644 sample_site/pages/index.html.hbs delete mode 100644 sample_site/theme/cream/standard/config.json delete mode 100644 sample_site/theme/cream/standard/markup/searchbar.hbs delete mode 100644 sample_site/theme/cream/standard/page.html.hbs delete mode 100644 sample_site/theme/cream/standard/script/core.hbs delete mode 100644 sample_site/theme/cream/standard/script/searchbar.hbs diff --git a/sample_site/config.json b/sample_site/config.json deleted file mode 100644 index 7a73a41b..00000000 --- a/sample_site/config.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/sample_site/config/global_config.json b/sample_site/config/global_config.json deleted file mode 100644 index 9e26dfee..00000000 --- a/sample_site/config/global_config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/sample_site/config/index.json b/sample_site/config/index.json deleted file mode 100644 index 9e26dfee..00000000 --- a/sample_site/config/index.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/sample_site/overrides/placeholder.hbs b/sample_site/overrides/placeholder.hbs deleted file mode 100644 index ff7bd09c..00000000 --- a/sample_site/overrides/placeholder.hbs +++ /dev/null @@ -1 +0,0 @@ -// placeholder diff --git a/sample_site/package-lock.json b/sample_site/package-lock.json deleted file mode 100644 index a80d1159..00000000 --- a/sample_site/package-lock.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "sample_site", - "version": "1.0.0", - "lockfileVersion": 1 -} diff --git a/sample_site/package.json b/sample_site/package.json deleted file mode 100644 index 071a02dc..00000000 --- a/sample_site/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "sample_site", - "version": "1.0.0", - "description": "test package", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC" -} diff --git a/sample_site/pages/index.html.hbs b/sample_site/pages/index.html.hbs deleted file mode 100644 index 321ac64a..00000000 --- a/sample_site/pages/index.html.hbs +++ /dev/null @@ -1 +0,0 @@ -// Universal Search Page diff --git a/sample_site/theme/cream/standard/config.json b/sample_site/theme/cream/standard/config.json deleted file mode 100644 index e69de29b..00000000 diff --git a/sample_site/theme/cream/standard/markup/searchbar.hbs b/sample_site/theme/cream/standard/markup/searchbar.hbs deleted file mode 100644 index e69de29b..00000000 diff --git a/sample_site/theme/cream/standard/page.html.hbs b/sample_site/theme/cream/standard/page.html.hbs deleted file mode 100644 index 14e9c717..00000000 --- a/sample_site/theme/cream/standard/page.html.hbs +++ /dev/null @@ -1,9 +0,0 @@ - -
-
- {{> experiences_standard_markup_searchbar }} -
-
\ No newline at end of file diff --git a/sample_site/theme/cream/standard/script/core.hbs b/sample_site/theme/cream/standard/script/core.hbs deleted file mode 100644 index e69de29b..00000000 diff --git a/sample_site/theme/cream/standard/script/searchbar.hbs b/sample_site/theme/cream/standard/script/searchbar.hbs deleted file mode 100644 index e69de29b..00000000 From d9ecec9ed8d66e53100278ee4e4fcafb540a5ef2 Mon Sep 17 00:00:00 2001 From: Oliver Shi Date: Mon, 7 Jun 2021 11:45:20 -0400 Subject: [PATCH 03/13] use npmlog for logging with colors (#228) This commit refactors jambo's logging to use npmlog, which is the logger used by npm (surprise!). It adds a magenta 'jambo' log heading to differentiate it from npm, which uses a white on black 'npm' heading. J=SLAP-226 TEST=manual test a regular jambo build test jambo build when it throws an error because there's no jambo.json test jambo page that is missing required arguments test the custom card command in the theme see that the tested logs look as expected, and contain the same information as before jambo help prints normally jambo version prints normally warning is given if custom command is malformatted test that you can set the npmlog log level anywhere, and that log level will be reflected globally in the future we can let jambo set different log levels or even silence logging --- package-lock.json | 404 +++++++++++++++++- package.json | 1 + src/buildJamboCLI.js | 3 +- src/cli.js | 10 +- src/commands/build/pagesetsbuilder.js | 9 +- src/commands/build/pagewriter.js | 5 +- src/commands/build/sitesgenerator.js | 21 +- src/commands/commandimporter.js | 4 +- .../jambotranslationextractor.js | 3 +- src/commands/import/themeimporter.js | 3 +- src/commands/upgrade/themeupgrader.js | 5 +- src/errors/systemerror.js | 2 +- src/errors/usererror.js | 2 +- src/models/configurationregistry.js | 3 +- src/utils/errorutils.js | 9 +- src/utils/logger.js | 34 ++ src/yargsfactory.js | 10 +- tests/acceptance/setup/TestInstance.js | 3 +- tests/acceptance/suites/basic-flow.js | 1 - 19 files changed, 473 insertions(+), 59 deletions(-) create mode 100644 src/utils/logger.js diff --git a/package-lock.json b/package-lock.json index 7cfe9e49..e0142a1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3189,6 +3189,18 @@ "jest-message-util": "^25.5.0", "jest-util": "^25.5.0", "slash": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@jest/core": { @@ -3227,6 +3239,16 @@ "strip-ansi": "^6.0.0" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -3303,6 +3325,16 @@ "v8-to-istanbul": "^4.1.3" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -3387,6 +3419,16 @@ "write-file-atomic": "^3.0.0" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -3405,6 +3447,18 @@ "@types/istanbul-reports": "^1.1.1", "@types/yargs": "^15.0.0", "chalk": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@nodelib/fs.scandir": { @@ -3740,6 +3794,44 @@ "picomatch": "^2.0.4" } }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3856,6 +3948,16 @@ "slash": "^3.0.0" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -4109,16 +4211,6 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -4164,6 +4256,11 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -4229,6 +4326,11 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -4423,6 +4525,11 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5179,6 +5286,54 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -5372,6 +5527,11 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -5759,8 +5919,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -5880,6 +6039,16 @@ "yargs": "^15.3.1" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -6050,6 +6219,16 @@ "realpath-native": "^2.0.0" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -6068,6 +6247,18 @@ "diff-sequences": "^25.2.6", "jest-get-type": "^25.2.6", "pretty-format": "^25.5.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-docblock": { @@ -6090,6 +6281,18 @@ "jest-get-type": "^25.2.6", "jest-util": "^25.5.0", "pretty-format": "^25.5.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-environment-jsdom": { @@ -6343,6 +6546,18 @@ "jest-util": "^25.5.0", "pretty-format": "^25.5.0", "throat": "^5.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-leak-detector": { @@ -6365,6 +6580,18 @@ "jest-diff": "^25.5.0", "jest-get-type": "^25.2.6", "pretty-format": "^25.5.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-message-util": { @@ -6383,6 +6610,16 @@ "stack-utils": "^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==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -6429,6 +6666,16 @@ "slash": "^3.0.0" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -6475,6 +6722,16 @@ "throat": "^5.0.0" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -6517,6 +6774,16 @@ "yargs": "^15.3.1" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -6622,6 +6889,16 @@ "semver": "^6.3.0" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -6643,6 +6920,16 @@ "make-dir": "^3.0.0" }, "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -6663,6 +6950,18 @@ "jest-get-type": "^25.2.6", "leven": "^3.1.0", "pretty-format": "^25.5.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-watcher": { @@ -6677,6 +6976,18 @@ "chalk": "^3.0.0", "jest-util": "^25.5.0", "string-length": "^3.1.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-worker": { @@ -7103,6 +7414,22 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -7115,6 +7442,11 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -7389,6 +7721,11 @@ } } }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -7889,8 +8226,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.1", @@ -7946,8 +8282,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==" }, "simple-git": { "version": "1.132.0", @@ -8834,6 +9169,43 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/package.json b/package.json index 48a8afa9..b9bf0e1c 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "i18next-conv": "^10.0.2", "lodash": "^4.17.15", "merge-options": "^2.0.0", + "npmlog": "^4.1.2", "prompts": "^2.3.1", "simple-git": "^1.131.0", "yargs": "^17.0.0" diff --git a/src/buildJamboCLI.js b/src/buildJamboCLI.js index 312f8400..20e945d8 100644 --- a/src/buildJamboCLI.js +++ b/src/buildJamboCLI.js @@ -4,6 +4,7 @@ const { parseJamboConfig } = require('./utils/jamboconfigutils'); const CommandRegistry = require('./commands/commandregistry'); const YargsFactory = require('./yargsfactory'); const CommandImporter = require('./commands/commandimporter'); +const { error } = require('./utils/logger'); /** * @param {string[]} argv the argv for the current process @@ -14,7 +15,7 @@ module.exports = function buildJamboCLI(argv) { const commandRegistry = new CommandRegistry(); if (argv.length < 3) { - console.error('You must provide Jambo with a command.'); + error('You must provide Jambo with a command.'); return; } diff --git a/src/cli.js b/src/cli.js index 6ac50131..09e2e5fc 100755 --- a/src/cli.js +++ b/src/cli.js @@ -3,12 +3,8 @@ const { exitWithError } = require('./utils/errorutils'); const buildJamboCLI = require('./buildJamboCLI'); // Exit with a non-zero exit code for unhandled rejections and uncaught exceptions -process.on('unhandledRejection', err => { - exitWithError(err); -}); -process.on('uncaughtException', err => { - exitWithError(err); -}); +process.on('unhandledRejection', exitWithError); +process.on('uncaughtException', exitWithError); const jambo = buildJamboCLI(process.argv); -jambo && jambo.parse(); +jambo && jambo.parseAsync().catch(exitWithError); diff --git a/src/commands/build/pagesetsbuilder.js b/src/commands/build/pagesetsbuilder.js index 68249e36..990f0fcd 100644 --- a/src/commands/build/pagesetsbuilder.js +++ b/src/commands/build/pagesetsbuilder.js @@ -6,6 +6,7 @@ const PageSet = require('../../models/pageset'); const PageTemplate = require('../../models/pagetemplate'); const PageTemplateDirector = require('./pagetemplatedirector'); const { NO_LOCALE } = require('../../constants'); +const { warn } = require('../../utils/logger'); /** * PageSetsBuilder is responsible for matching {@link PageConfigs} and @@ -47,8 +48,8 @@ module.exports = class PageSetsBuilder { const localeMessage = locale !== NO_LOCALE ? ` for '${locale}' locale` : ''; - console.log( - `Warning: No page templates found${localeMessage}, not generating a ` + + warn( + `No page templates found${localeMessage}, not generating a ` + `page set${localeMessage}`); continue; } @@ -80,8 +81,8 @@ module.exports = class PageSetsBuilder { const localeMessage = config.getLocale() !== NO_LOCALE ? ` found for '${config.getLocale()}' locale` : ''; - console.log( - `Warning: No page template '${config.getPageName()}'${localeMessage}, ` + + warn( + `No page template '${config.getPageName()}'${localeMessage}, ` + `not generating a '${config.getPageName()}' page${localeMessage}`); continue; } diff --git a/src/commands/build/pagewriter.js b/src/commands/build/pagewriter.js index 161d035d..ff32d884 100644 --- a/src/commands/build/pagewriter.js +++ b/src/commands/build/pagewriter.js @@ -7,6 +7,7 @@ const UserError = require('../../errors/usererror'); const { NO_LOCALE } = require('../../constants'); const LocalizationConfig = require('../../models/localizationconfig'); const TemplateArgsBuilder = require('./templateargsbuilder'); +const { info } = require('../../utils/logger'); /** * PageWriter is responsible for writing output files for the given {@link PageSet} to @@ -43,14 +44,14 @@ module.exports = class PageWriter { const localeMessage = pageSet.getLocale() !== NO_LOCALE ? ` for '${pageSet.getLocale()}' locale` : ''; - console.log(`Writing files${localeMessage}`); + info(`Writing files${localeMessage}`); for (const page of pageSet.getPages()) { if (!page.getConfig()) { throw new UserError(`Error: No config found for page: ${page.getName()}`); } - console.log(`Writing output file for the '${page.getName()}' page`); + info(`Writing output file for the '${page.getName()}' page`); const templateArguments = this._templateArgsBuilder.buildArgs({ relativePath: this._calculateRelativePath(page.getOutputPath()), pageName: page.getName(), diff --git a/src/commands/build/sitesgenerator.js b/src/commands/build/sitesgenerator.js index 48f9a4de..a6b1a2fa 100644 --- a/src/commands/build/sitesgenerator.js +++ b/src/commands/build/sitesgenerator.js @@ -21,6 +21,7 @@ const registerCustomHbsHelpers = require('../../handlebars/registercustomhbshelp const SystemError = require('../../errors/systemerror'); const Translator = require('../../i18n/translator/translator'); const UserError = require('../../errors/usererror'); +const { info } = require('../../utils/logger'); class SitesGenerator { constructor(jamboConfig) { @@ -44,9 +45,9 @@ class SitesGenerator { // Pull all data from environment variables. const envVarParser = EnvironmentVariableParser.create(); const env = envVarParser.parse(['JAMBO_INJECTED_DATA'].concat(jsonEnvVars)); - console.log('Jambo Injected Data:', env['JAMBO_INJECTED_DATA']); + info('Jambo Injected Data:', env['JAMBO_INJECTED_DATA']); - console.log('Reading config files'); + info('Reading config files'); const configNameToRawConfig = {}; fs.recurseSync(config.dirs.config, (path, relative, filename) => { if (isValidFile(filename)) { @@ -86,7 +87,7 @@ class SitesGenerator { } }); - console.log('Reading partial files'); + info('Reading partial files'); let partialRegistry; try { partialRegistry = PartialsRegistry.build({ @@ -111,7 +112,7 @@ class SitesGenerator { new PageUniquenessValidator().validate(allPages); // Clear the output directory but keep preserved files before writing new files - console.log('Cleaning output directory'); + info('Cleaning output directory'); const shouldCleanOutput = fs.existsSync(config.dirs.output) && !(this._isPreserved(config.dirs.output, config.dirs.preservedFiles)); @@ -119,20 +120,20 @@ class SitesGenerator { this._clearDirectory(config.dirs.output, config.dirs.preservedFiles); } - console.log('Creating static output directory'); + info('Creating static output directory'); let staticDirs = [ `${config.dirs.themes}/${config.defaultTheme}/static`, 'static' ]; this._createStaticOutput(staticDirs, config.dirs.output); - console.log('Extracting translations'); + info('Extracting translations'); const locales = GENERATED_DATA.getLocales(); const translations = await this._extractTranslations(locales, configRegistry.getLocalizationConfig()); // Register built-in Jambo Handlebars helpers. - console.log('Registering Jambo Handlebars helpers'); + info('Registering Jambo Handlebars helpers'); try { registerHbsHelpers(hbs); } catch (err) { @@ -144,7 +145,7 @@ class SitesGenerator { path.resolve(config.dirs.themes, config.defaultTheme, 'hbshelpers') if (fs.existsSync(customHbsHelpersDir)) { try { - console.log('Registering custom Handlebars helpers from the default theme'); + info('Registering custom Handlebars helpers from the default theme'); registerCustomHbsHelpers(hbs, customHbsHelpersDir); } catch (err) { throw new UserError('Failed to register custom handlebars helpers', err.stack); @@ -159,7 +160,7 @@ class SitesGenerator { .create(locale, GENERATED_DATA.getLocaleFallbacks(locale), translations); const handlebarsPreprocessor = new HandlebarsPreprocessor(translator); - console.log(`Registering Handlebars partials for locale ${locale}`); + info(`Registering Handlebars partials for locale ${locale}`); for (const partial of partialRegistry.getPartials()) { hbs.registerPartial( partial.getName(), @@ -185,7 +186,7 @@ class SitesGenerator { }).writePages(pageSet); } - console.log('Done.'); + info('Done.'); } /** diff --git a/src/commands/commandimporter.js b/src/commands/commandimporter.js index 1a8aaa0f..a51c0d5a 100644 --- a/src/commands/commandimporter.js +++ b/src/commands/commandimporter.js @@ -1,5 +1,6 @@ const path = require('path'); const fs = require('fs-extra'); +const { warn } = require('../utils/logger'); /** * Imports all custom {@link Command}s within a Jambo repository. @@ -40,8 +41,7 @@ class CommandImporter { if (this._validateCustomCommand(commandClass)) { customCommands.push(commandClass); } else { - console.warn( - `Command in ${path.basename(filePath)} was not formatted properly`); + warn(`Command in ${path.basename(filePath)} was not formatted properly`); } }); diff --git a/src/commands/extract-translations/jambotranslationextractor.js b/src/commands/extract-translations/jambotranslationextractor.js index 29dce8fe..184e39e0 100644 --- a/src/commands/extract-translations/jambotranslationextractor.js +++ b/src/commands/extract-translations/jambotranslationextractor.js @@ -2,6 +2,7 @@ const TranslationExtractor = require('../../i18n/extractor/translationextractor' const { ArgumentMetadata, ArgumentType } = require('../../models/commands/argumentmetadata'); const fsExtra = require('fs-extra'); const fs = require('fs'); +const { info } = require('../../utils/logger'); /** * JamboTranslationExtractor extracts translations from a jambo repo. @@ -54,7 +55,7 @@ class JamboTranslationExtractor { async _extract(outputPath) { const { files, directories } = this._getFilesAndDirsFromJamboConfig(); const gitignorePaths = this._parseGitignorePaths(); - console.log(`Extracting translations to ${outputPath}`); + info(`Extracting translations to ${outputPath}`); this.extractor.extract({ specificFiles: files, directories: directories, diff --git a/src/commands/import/themeimporter.js b/src/commands/import/themeimporter.js index be50c13c..dcbe254e 100644 --- a/src/commands/import/themeimporter.js +++ b/src/commands/import/themeimporter.js @@ -73,8 +73,7 @@ class ThemeImporter { } async execute(args) { - await this.import(args.themeUrl, args.theme, args.useSubmodules) - .then(console.log); + await this.import(args.themeUrl, args.theme, args.useSubmodules); } /** diff --git a/src/commands/upgrade/themeupgrader.js b/src/commands/upgrade/themeupgrader.js index 96474710..123a0524 100644 --- a/src/commands/upgrade/themeupgrader.js +++ b/src/commands/upgrade/themeupgrader.js @@ -11,6 +11,7 @@ const UserError = require('../../errors/systemerror'); const { isCustomError } = require('../../utils/errorutils'); const { searchDirectoryIgnoringExtensions } = require('../../utils/fileutils'); const fsExtra = require('fs-extra'); +const { info } = require('../../utils/logger'); const git = simpleGit(); @@ -114,11 +115,11 @@ class ThemeUpgrader { this._executePostUpgradeScript(themePath, isLegacy); } if (isLegacy) { - console.log( + info( 'Legacy theme upgrade complete. \n' + 'You may need to manually reinstall dependencies (e.g. an npm install).'); } else { - console.log( + info( 'Theme upgrade complete. \n' + 'You may need to manually reinstall dependencies (e.g. an npm install).'); } diff --git a/src/errors/systemerror.js b/src/errors/systemerror.js index 3e93f38c..8f186cc2 100644 --- a/src/errors/systemerror.js +++ b/src/errors/systemerror.js @@ -10,8 +10,8 @@ class SystemError extends Error { this.exitCode = 14; if (stack) { - this.stack = stack; this.message = `${this.name}: ${message}`; + this.stack = `${this.message}\n${stack}`; } else { Error.captureStackTrace(this, this.constructor); } diff --git a/src/errors/usererror.js b/src/errors/usererror.js index 19b2ed28..117bfc05 100644 --- a/src/errors/usererror.js +++ b/src/errors/usererror.js @@ -9,8 +9,8 @@ class UserError extends Error { this.exitCode = 13; if (stack) { - this.stack = stack; this.message = `${this.name}: ${message}`; + this.stack = `${this.message}\n${stack}`; } else { Error.captureStackTrace(this, this.constructor); } diff --git a/src/models/configurationregistry.js b/src/models/configurationregistry.js index b9a354dd..fa7c255e 100644 --- a/src/models/configurationregistry.js +++ b/src/models/configurationregistry.js @@ -5,6 +5,7 @@ const LocalizationConfig = require('./localizationconfig'); const PageConfig = require('./pageconfig'); const { FileNames, ConfigKeys } = require('../constants'); const RawConfigValidator = require('../validation/rawconfigvalidator'); +const { info } = require('../utils/logger'); /** * ConfigurationRegistry is a registry of the configuration files provided to Jambo. @@ -88,7 +89,7 @@ module.exports = class ConfigurationRegistry { const rawLocaleConfig = configNameToRawConfig[ConfigKeys.LOCALE_CONFIG]; if (!rawLocaleConfig) { - console.log( + info( `Cannot find '${FileNames.LOCALE_CONFIG}', using locale information ` + `from ${FileNames.GLOBAL_CONFIG}.`); } diff --git a/src/utils/errorutils.js b/src/utils/errorutils.js index 73bed3e6..6c8f670d 100644 --- a/src/utils/errorutils.js +++ b/src/utils/errorutils.js @@ -1,6 +1,6 @@ -const fs = require('fs'); const UserError = require('../errors/usererror'); const SystemError = require('../errors/systemerror'); +const { error } = require('./logger'); /** * Print the error, and then forcefully end the @@ -8,11 +8,8 @@ const SystemError = require('../errors/systemerror'); * async operations will be lost. * @param {Error} error */ -exports.exitWithError = (err) => { - fs.writeSync(process.stderr.fd, - `${err.message}\n` + - `${err.stack}` - ); +exports.exitWithError = (err = {}) => { + err.stack && error(err.stack); const exitCode = err.exitCode || 1; process.exit(exitCode); } diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 00000000..3963f922 --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,34 @@ +const log = require('npmlog'); + +log.heading = 'jambo'; +log.headingStyle = { fg: 'magenta' }; + +// Don't display a log prefix. +const PREFIX = ''; + +/** + * Logs an error. + * + * @param {...any} args + */ +exports.error = function(...args) { + log.error(PREFIX, ...args) +} + +/** + * Logs a warning. + * + * @param {...any} args + */ +exports.warn = function(...args) { + log.warn(PREFIX, ...args) +} + +/** + * Logs info. + * + * @param {...any} args + */ +exports.info = function(...args) { + log.info(PREFIX, ...args) +} diff --git a/src/yargsfactory.js b/src/yargsfactory.js index e8019959..41f1c8a1 100644 --- a/src/yargsfactory.js +++ b/src/yargsfactory.js @@ -2,6 +2,8 @@ const yargs = require('yargs'); const PageScaffolder = require('./commands/page/add/pagescaffolder'); const SitesGenerator = require('./commands/build/sitesgenerator'); const { ArgumentMetadata, ArgumentType } = require('./models/commands/argumentmetadata'); +const { info, error } = require('./utils/logger'); +const { exitWithError } = require('./utils/errorutils'); /** * Creates the {@link yargs} instance that powers the Jambo CLI. @@ -19,7 +21,13 @@ class YargsFactory { * @returns {import('yargs').Argv} */ createCLI() { - const cli = yargs.usage('Usage: $0 [options]'); + const cli = yargs + .usage('Usage: $0 [options]') + .fail((msg, err, yargs) => { + info(yargs.help()); + if (msg) error(msg); + exitWithError(err); + }); this._commandRegistry.getCommands().forEach(commandClass => { cli.command(this._createCommandModule(commandClass)); diff --git a/tests/acceptance/setup/TestInstance.js b/tests/acceptance/setup/TestInstance.js index 4d0ff8dc..b1153315 100644 --- a/tests/acceptance/setup/TestInstance.js +++ b/tests/acceptance/setup/TestInstance.js @@ -1,5 +1,6 @@ const buildJamboCLI = require('../../../src/buildJamboCLI'); const { parse } = require('shell-quote'); +const { error } = require('../../../src/utils/logger'); /** * TestInstance gives Jambo acceptance tests different ways @@ -13,7 +14,7 @@ module.exports = class TestInstance { return buildJamboCLI(argv) .scriptName('jambo') .fail(function(msg, err) { - console.error('Error running command:', command); + error('Error running command:', command); if (err) throw err }) .exitProcess(false) diff --git a/tests/acceptance/suites/basic-flow.js b/tests/acceptance/suites/basic-flow.js index 7432252e..3d864760 100644 --- a/tests/acceptance/suites/basic-flow.js +++ b/tests/acceptance/suites/basic-flow.js @@ -1,5 +1,4 @@ const fs = require('fs'); -const path = require('path'); const { runInPlayground } = require('../setup/playground'); From 5a4afc79ba2863d676c5d44df9962651a17c3ffc Mon Sep 17 00:00:00 2001 From: Yen Truong <36055303+yen-tt@users.noreply.github.com> Date: Tue, 8 Jun 2021 16:39:13 -0400 Subject: [PATCH 04/13] Add Jambo build validation hook (#229) * Add Jambo build validation hook - Added another operation inside PageWriter to ensure template data is correct using TemplateDataValidator, which pass in the templateArgument to the theme's validation hook function if it exists. Throw error if theme's hook function return false indicating bad template data. J=SLAP-1369 TEST=auto Passed Jest test using default config format with/without missing field when pass to validation hook Co-authored-by: Yen Truong --- src/commands/build/pagewriter.js | 18 ++- src/commands/build/sitesgenerator.js | 3 + src/commands/build/templatedatavalidator.js | 48 ++++++++ tests/commands/build/templatedatavalidator.js | 107 ++++++++++++++++++ tests/fixtures/hooks/templatedatavalidator.js | 18 +++ 5 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 src/commands/build/templatedatavalidator.js create mode 100644 tests/commands/build/templatedatavalidator.js create mode 100644 tests/fixtures/hooks/templatedatavalidator.js diff --git a/src/commands/build/pagewriter.js b/src/commands/build/pagewriter.js index ff32d884..d60e3d76 100644 --- a/src/commands/build/pagewriter.js +++ b/src/commands/build/pagewriter.js @@ -7,6 +7,7 @@ const UserError = require('../../errors/usererror'); const { NO_LOCALE } = require('../../constants'); const LocalizationConfig = require('../../models/localizationconfig'); const TemplateArgsBuilder = require('./templateargsbuilder'); +const TemplateDataValidator = require('./templatedatavalidator'); const { info } = require('../../utils/logger'); /** @@ -30,12 +31,20 @@ module.exports = class PageWriter { */ this._templateArgsBuilder = new TemplateArgsBuilder(config.templateDataFormatterHook); + + /** + * @type {TemplateDataValidator} + */ + this._templateDataValidator = + new TemplateDataValidator(config.templateDataValidationHook); } /** * Writes a file to the output directory per page in the given PageSet. * * @param {PageSet} pageSet the collection of pages to generate + * @throws {UserError} on missing page config(s), validation hook execution + * failure, and invalid template data using Theme's validation hook */ writePages(pageSet) { if (!pageSet || pageSet.getPages().length < 1) { @@ -51,7 +60,6 @@ module.exports = class PageWriter { throw new UserError(`Error: No config found for page: ${page.getName()}`); } - info(`Writing output file for the '${page.getName()}' page`); const templateArguments = this._templateArgsBuilder.buildArgs({ relativePath: this._calculateRelativePath(page.getOutputPath()), pageName: page.getName(), @@ -61,7 +69,15 @@ module.exports = class PageWriter { locale: pageSet.getLocale(), env: this._env }); + + if(!this._templateDataValidator.validate({ + pageName: page.getName(), + pageData: templateArguments + })) { + throw new UserError('Invalid page template configuration(s).'); + } + info(`Writing output file for the '${page.getName()}' page`); const template = hbs.compile(page.getTemplateContents()); const outputHTML = template(templateArguments); diff --git a/src/commands/build/sitesgenerator.js b/src/commands/build/sitesgenerator.js index a6b1a2fa..f7f9ae2e 100644 --- a/src/commands/build/sitesgenerator.js +++ b/src/commands/build/sitesgenerator.js @@ -178,10 +178,13 @@ class SitesGenerator { const templateDataFormatterHook = path.resolve( config.dirs.themes, config.defaultTheme, 'hooks', 'templatedataformatter.js'); + const templateDataValidationHook = path.resolve( + config.dirs.themes, config.defaultTheme, 'hooks', 'templatedatavalidator.js'); // Write pages new PageWriter({ outputDirectory: config.dirs.output, templateDataFormatterHook: templateDataFormatterHook, + templateDataValidationHook: templateDataValidationHook, env: env, }).writePages(pageSet); } diff --git a/src/commands/build/templatedatavalidator.js b/src/commands/build/templatedatavalidator.js new file mode 100644 index 00000000..e811ceb2 --- /dev/null +++ b/src/commands/build/templatedatavalidator.js @@ -0,0 +1,48 @@ +const fs = require('file-system'); +const UserError = require('../../errors/usererror'); +const { isCustomError } = require('../../utils/errorutils'); +const { info } = require('../../utils/logger'); + +/** + * TemplateDataValidator is reponsible for checking data supplied to a page + * using Theme's custom validation steps (if any). + */ +module.exports = class TemplateDataValidator { + constructor(templateDataValidationHook) { + /** + * The path to template data validation hook. + * @type {string} + */ + this._templateDataValidationHook = templateDataValidationHook; + + /** + * Whether or not the file for template data validation hook exists + * @type {boolean} + */ + this._hasHook = fs.existsSync(this._templateDataValidationHook); + } + + /** + * Execute validation hook's function if file exists + * + * @param {Object} page + * @param {string} page.pageName name of the current page + * @param {Object} page.pageData template arguments for the current page + * @throws {UserError} on failure to execute hook + * @returns {boolean} whether or not to throw an exception on bad template arguments + */ + validate({ pageName, pageData }) { + if (!this._hasHook) { + return true; + } + try { + info(`Validating configuration for page "${pageName}".`); + const validatorFunction = require(this._templateDataValidationHook); + return validatorFunction(pageData); + } catch (err) { + const msg = + `Error executing validation hook from ${this._templateDataValidationHook}: `; + throw new UserError(msg, err.stack); + } + } +} diff --git a/tests/commands/build/templatedatavalidator.js b/tests/commands/build/templatedatavalidator.js new file mode 100644 index 00000000..001259b0 --- /dev/null +++ b/tests/commands/build/templatedatavalidator.js @@ -0,0 +1,107 @@ +const TemplateDataValidator = require('../../../src/commands/build/templatedatavalidator'); +const path = require('path'); +const UserError = require('../../../src/errors/usererror'); + +describe('TemplateDataValidator validates config data using hook properly', () => { + const currentPageConfig = { + url: 'examplePage.html', + verticalKey: 'examplePage', + pageTitle: 'Example Page', + pageSettings: { search: { verticalKey: 'examplePage', defaultInitialSearch: '' } }, + componentSettings: { + prop: 'example1', + }, + verticalsToConfig: { + examplePage: { + prop: 'example2' + } + } + }; + const verticalConfigs = { + page1: { + config: { + prop: 'example' + } + }, + page2: { + config: { + prop: 'example2' + } + } + }; + const global_config = { + sdkVersion: 'X.X', + apiKey: 'exampleKey', + experienceKey: 'slanswers', + locale: 'en' + }; + const params = { + example: 'param' + }; + const relativePath = '..'; + const env = { + envVar: 'envVar', + }; + + it('does not throw an error with a correct config', () => { + const templateDataValidationHook = path.resolve( + __dirname, '../../fixtures/hooks/templatedatavalidator.js'); + const templateData = { + currentPageConfig, + verticalConfigs : verticalConfigs, + global_config : global_config, + params : params, + relativePath: relativePath, + env: env + }; + + const isValid = new TemplateDataValidator(templateDataValidationHook).validate({ + pageName: 'examplePage', + pageData: templateData + }); + expect(isValid).toEqual(true); + }); + + it('throws an error when a field in config is missing', () => { + const templateDataValidationHook = path.resolve( + __dirname, '../../fixtures/hooks/templatedatavalidator.js'); + const global_config_missing_key = {}; + const templateData = { + currentPageConfig, + verticalConfigs : verticalConfigs, + global_config : global_config_missing_key, + params : params, + relativePath: relativePath, + env: env + }; + + const isValid = new TemplateDataValidator(templateDataValidationHook).validate({ + pageName: 'examplePage', + pageData: templateData + }); + expect(isValid).toEqual(false); + + }); + + + it('does not throw error, gracefully ignore missing config field in bad pages', () => { + const templateDataValidationHook = path.resolve( + __dirname, '../../fixtures/hooks/templatedatavalidator.js'); + const params_missing_field = {}; + const templateData = { + currentPageConfig, + verticalConfigs : verticalConfigs, + global_config : global_config, + params : params_missing_field, + relativePath: relativePath, + env: env + }; + + const isValid = new TemplateDataValidator(templateDataValidationHook).validate({ + pageName: 'examplePage', + pageData: templateData + }); + expect(isValid).toEqual(true); + }); + +}); diff --git a/tests/fixtures/hooks/templatedatavalidator.js b/tests/fixtures/hooks/templatedatavalidator.js new file mode 100644 index 00000000..17044b0f --- /dev/null +++ b/tests/fixtures/hooks/templatedatavalidator.js @@ -0,0 +1,18 @@ +const { warn } = require('../../../src/utils/logger'); +/** + * A test data validator hook. + * + * @param {Object} pageData configuration(s) of a page template + * @returns {boolean} false if validator should throw an error + */ + module.exports = function (pageData) { + if(!pageData["params"]["example"]) { + warn('Missing Info: param example in config file(s)'); + return true; //gracefully ignore missing param on page + } + if(!pageData["global_config"]["experienceKey"]) { + warn('Missing Info: experienceKey in config file(s)'); + return false; + } + return true; +} From 75c25eaf243033253f072cca654526b41d0a634c Mon Sep 17 00:00:00 2001 From: cjiang2000 <44913744+cjiang2000@users.noreply.github.com> Date: Wed, 23 Jun 2021 11:15:00 -0400 Subject: [PATCH 05/13] Created Jambo Describe Acceptance Test. (#231) * Created Jambo Describe Acceptance Test. J=SLAP-1305 TEST=auto Tested the acceptance test by running it from terminal. * fixed eslint issues * fixed toms mistake * fixed some styling issues and removed the inside of the execute function * moved process.env.IS_DEVELOPMENT_PREVIEW and changed test name --- src/commands/commandimporter.js | 5 +- src/commands/page/add/pagecommand.js | 4 +- .../fixtures/describe/describe-test.json | 140 ++++++++++++++++++ tests/acceptance/suites/describe.js | 14 ++ .../describe/commands/addVertical.js | 89 +++++++++++ .../test-themes/describe/postimport.js | 18 +++ .../describe-template/page-config.json | 1 + .../templates/describe-template/page.html.hbs | 1 + tests/setup/setup.js | 1 + 9 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 tests/acceptance/fixtures/describe/describe-test.json create mode 100644 tests/acceptance/suites/describe.js create mode 100644 tests/acceptance/test-themes/describe/commands/addVertical.js create mode 100755 tests/acceptance/test-themes/describe/postimport.js create mode 100644 tests/acceptance/test-themes/describe/templates/describe-template/page-config.json create mode 100644 tests/acceptance/test-themes/describe/templates/describe-template/page.html.hbs diff --git a/src/commands/commandimporter.js b/src/commands/commandimporter.js index a51c0d5a..40cad3bc 100644 --- a/src/commands/commandimporter.js +++ b/src/commands/commandimporter.js @@ -23,17 +23,16 @@ class CommandImporter { let commandDirectories = ['commands']; this._themeDir && commandDirectories.unshift(path.join(this._themeDir, 'commands')); commandDirectories = commandDirectories.filter(fs.existsSync); - let customCommands = []; if (commandDirectories.length > 0) { const mergedDirectory = this._mergeCommandDirectories(commandDirectories); + const currDirectory = process.cwd(); fs.readdirSync(mergedDirectory) - .map(directoryPath => path.resolve(mergedDirectory, directoryPath)) + .map(directoryPath => path.resolve(currDirectory, mergedDirectory, directoryPath)) .filter(directoryPath => directoryPath.endsWith('.js')) .filter(directoryPath => fs.lstatSync(directoryPath).isFile()) .forEach(filePath => { const requiredModule = require(filePath); - const commandClass = this._isLegacyImport(requiredModule) ? this._handleLegacyImport(requiredModule) : requiredModule; diff --git a/src/commands/page/add/pagecommand.js b/src/commands/page/add/pagecommand.js index e0dfb82c..39f5cce6 100644 --- a/src/commands/page/add/pagecommand.js +++ b/src/commands/page/add/pagecommand.js @@ -78,7 +78,9 @@ class PageCommand { if (!defaultTheme || !themesDir) { return []; } - const pageTemplatesDir = path.resolve(themesDir, defaultTheme, 'templates'); + const currDirectory = process.cwd(); + const pageTemplatesDir = + path.resolve(currDirectory, themesDir, defaultTheme, 'templates'); return fs.readdirSync(pageTemplatesDir); } diff --git a/tests/acceptance/fixtures/describe/describe-test.json b/tests/acceptance/fixtures/describe/describe-test.json new file mode 100644 index 00000000..6d403826 --- /dev/null +++ b/tests/acceptance/fixtures/describe/describe-test.json @@ -0,0 +1,140 @@ +{ + "init": { + "displayName": "Initialize Jambo", + "params": { + "themeUrl": { + "displayName": "URL", + "type": "string" + }, + "theme": { + "displayName": "Theme", + "type": "singleoption", + "options": [ + "answers-hitchhiker-theme" + ] + }, + "useSubmodules": { + "displayName": "Use Submodules", + "type": "boolean" + } + } + }, + "import": { + "displayName": "Import Theme", + "params": { + "themeUrl": { + "displayName": "URL", + "type": "string" + }, + "theme": { + "displayName": "Theme", + "type": "singleoption", + "options": [ + "answers-hitchhiker-theme" + ] + }, + "useSubmodules": { + "displayName": "Use Submodules", + "type": "boolean" + } + } + }, + "page": { + "displayName": "Add Page", + "params": { + "name": { + "displayName": "Page Name", + "type": "string", + "required": true + }, + "template": { + "displayName": "Page Template", + "type": "singleoption", + "options": [ + "describe-template" + ] + }, + "locales": { + "displayName": "Additional Page Locales", + "type": "multioption", + "options": [] + } + } + }, + "override": { + "displayName": "Override Theme", + "params": { + "path": { + "displayName": "Path to Override", + "type": "filesystem", + "required": true, + "options": [ + "commands/addVertical.js", + "postimport.js", + "templates/describe-template/page-config.json", + "templates/describe-template/page.html.hbs" + ] + } + } + }, + "build": { + "displayName": "Build Pages" + }, + "extract-translations": { + "displayName": "Extract Translations", + "params": { + "output": { + "displayName": "Output Path", + "type": "string", + "required": false, + "default": "messages.pot" + } + } + }, + "vertical": { + "displayName": "Add Vertical", + "params": { + "name": { + "displayName": "Page Name", + "required": true, + "type": "string" + }, + "verticalKey": { + "displayName": "Vertical Key", + "required": true, + "type": "string" + }, + "cardName": { + "displayName": "Card Name", + "type": "singleoption" + }, + "template": { + "displayName": "Page Template", + "required": true, + "type": "singleoption" + }, + "locales": { + "displayName": "Additional Page Locales", + "type": "multioption" + } + } + }, + "upgrade": { + "displayName": "Upgrade Theme", + "params": { + "isLegacy": { + "displayName": "Is Legacy Upgrade", + "type": "boolean" + }, + "disableScript": { + "displayName": "Disable Upgrade Script", + "type": "boolean" + }, + "branch": { + "displayName": "Branch of theme to upgrade to", + "type": "string", + "default": "master" + } + } + } +} \ No newline at end of file diff --git a/tests/acceptance/suites/describe.js b/tests/acceptance/suites/describe.js new file mode 100644 index 00000000..b3aa4119 --- /dev/null +++ b/tests/acceptance/suites/describe.js @@ -0,0 +1,14 @@ +const fs = require('fs'); + +const { runInPlayground } = require('../setup/playground'); + +console.log = jest.fn(); + +it('describe on default and custom commands', () => runInPlayground(async t => { + await t.jambo('init'); + await t.jambo('import --themeUrl ../test-themes/describe'); + await t.jambo('describe'); + const expectedJSON = + fs.readFileSync('../fixtures/describe/describe-test.json', 'utf-8'); + expect(console.log).toHaveBeenCalledWith(expectedJSON); +})); \ No newline at end of file diff --git a/tests/acceptance/test-themes/describe/commands/addVertical.js b/tests/acceptance/test-themes/describe/commands/addVertical.js new file mode 100644 index 00000000..58f9aaab --- /dev/null +++ b/tests/acceptance/test-themes/describe/commands/addVertical.js @@ -0,0 +1,89 @@ +const { ArgumentMetadata, ArgumentType } = require('../../../../../src/models/commands/argumentmetadata'); + +/** + * VerticalAdder represents the `vertical` custom jambo command. The command adds + * a new page for the given Vertical and associates a card type with it. + */ +class VerticalAdder { + constructor(jamboConfig) { + this.config = jamboConfig; + } + + /** + * @returns {string} the alias for the add vertical command. + */ + static getAlias() { + return 'vertical'; + } + + /** + * @returns {string} a short description of the add vertical command. + */ + static getShortDescription() { + return 'create the page for a vertical'; + } + + /** + * @returns {Object} description of each argument for + * the add vertical command, keyed by name + */ + static args() { + return { + name: new ArgumentMetadata( + ArgumentType.STRING, 'name of the vertical\'s page', true), + verticalKey: new ArgumentMetadata(ArgumentType.STRING, 'the vertical\'s key', true), + cardName: new ArgumentMetadata( + ArgumentType.STRING, 'card to use with vertical', false), + template: new ArgumentMetadata( + ArgumentType.STRING, 'page template to use within theme', true), + locales: new ArgumentMetadata( + ArgumentType.ARRAY, + 'additional locales to generate the page for', + false, + [], + ArgumentType.STRING) + }; + } + + /** + * @returns {Object} description of the vertical command and its parameters. + */ + static describe(jamboConfig) { + return { + displayName: 'Add Vertical', + params: { + name: { + displayName: 'Page Name', + required: true, + type: 'string' + }, + verticalKey: { + displayName: 'Vertical Key', + required: true, + type: 'string', + }, + cardName: { + displayName: 'Card Name', + type: 'singleoption' + }, + template: { + displayName: 'Page Template', + required: true, + type: 'singleoption' + }, + locales: { + displayName: 'Additional Page Locales', + type: 'multioption' + } + } + }; + } + +/** + * Executes the add vertical command with the provided arguments. + * + * @param {Object} args The arguments, keyed by name + */ + execute(args) {} +} +module.exports = VerticalAdder; \ No newline at end of file diff --git a/tests/acceptance/test-themes/describe/postimport.js b/tests/acceptance/test-themes/describe/postimport.js new file mode 100755 index 00000000..21967e3f --- /dev/null +++ b/tests/acceptance/test-themes/describe/postimport.js @@ -0,0 +1,18 @@ +#!/usr/bin/env node +const fs = require('fs'); +const { assign, stringify } = require('comment-json'); + +/** + * Updates the defaultTheme field in the jambo.json. + * + * @param {string} themeName + */ +function updateDefaultTheme(themeName) { + const jamboConfig = JSON.parse(fs.readFileSync('jambo.json', 'utf-8')); + if (jamboConfig.defaultTheme !== themeName) { + const updatedConfig = assign({ defaultTheme: themeName }, jamboConfig); + fs.writeFileSync('jambo.json', stringify(updatedConfig, null, 2)); + } +} + +updateDefaultTheme('describe'); \ No newline at end of file diff --git a/tests/acceptance/test-themes/describe/templates/describe-template/page-config.json b/tests/acceptance/test-themes/describe/templates/describe-template/page-config.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/acceptance/test-themes/describe/templates/describe-template/page-config.json @@ -0,0 +1 @@ +{} diff --git a/tests/acceptance/test-themes/describe/templates/describe-template/page.html.hbs b/tests/acceptance/test-themes/describe/templates/describe-template/page.html.hbs new file mode 100644 index 00000000..281c6866 --- /dev/null +++ b/tests/acceptance/test-themes/describe/templates/describe-template/page.html.hbs @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/tests/setup/setup.js b/tests/setup/setup.js index 84391a3d..15eeedc4 100644 --- a/tests/setup/setup.js +++ b/tests/setup/setup.js @@ -1,4 +1,5 @@ const prettier = require('prettier'); +process.env.IS_DEVELOPMENT_PREVIEW = 'false'; expect.extend({ toEqualHtml: function(received, expected) { From 692c08c81177c44a290df337fe1c175d2a3a4a1a Mon Sep 17 00:00:00 2001 From: Oliver Shi Date: Thu, 24 Jun 2021 13:25:16 -0400 Subject: [PATCH 06/13] acceptance framework improvements (#232) This commit adds acceptance framework improvements needed to add acceptance tests for the following PR, which will add --globs as an argument to the extract-translations command. - adds globalSetup/globalTeardown to jest, which will do one time setup for the test-themes. Trying to do it in runPlayground() caused issues where different jest test instances would try to perform git operations on the same test theme and clobber each other. This is preferable to running tests serially with --runInBand - sets jambo's log level to 'silent' for acceptance tests, because its noisy by default - add procrastinateCleanup flag to runInPlayground for debugging J=SLAP-1378 TEST=auto reran tests --- package.json | 2 ++ src/utils/logger.js | 7 +++++ tests/acceptance/setup/playground.js | 43 +++------------------------ tests/acceptance/suites/basic-flow.js | 3 -- tests/acceptance/suites/describe.js | 5 ++-- tests/setup/globalsetup.js | 5 ++++ tests/setup/globalteardown.js | 6 ++++ tests/setup/setup.js | 5 +++- tests/setup/themesetup.js | 38 +++++++++++++++++++++++ 9 files changed, 69 insertions(+), 45 deletions(-) create mode 100644 tests/setup/globalsetup.js create mode 100644 tests/setup/globalteardown.js create mode 100644 tests/setup/themesetup.js diff --git a/package.json b/package.json index b9bf0e1c..5b0c59b3 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,8 @@ "src/**" ], "verbose": true, + "globalSetup": "./tests/setup/globalsetup.js", + "globalTeardown": "./tests/setup/globalteardown.js", "setupFilesAfterEnv": [ "./tests/setup/setup.js" ], diff --git a/src/utils/logger.js b/src/utils/logger.js index 3963f922..01e74bd6 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -6,6 +6,13 @@ log.headingStyle = { fg: 'magenta' }; // Don't display a log prefix. const PREFIX = ''; +/** + * Sets the global logging level. + */ +exports.setLogLevel = function(level) { + log.level = level; +} + /** * Logs an error. * diff --git a/tests/acceptance/setup/playground.js b/tests/acceptance/setup/playground.js index c8601fbd..02cb64de 100644 --- a/tests/acceptance/setup/playground.js +++ b/tests/acceptance/setup/playground.js @@ -1,63 +1,28 @@ const path = require('path'); -const fs = require('fs'); const fsExtra = require('fs-extra'); const { chdir, cwd } = require('process'); const TestInstance = require('./TestInstance'); -const simpleGit = require('simple-git/promise'); - -/** - * Transform all theme folders under test-themes/ into git repos. - */ -async function setupTestThemes() { - async function initGitRepo(dir) { - const originalDir = cwd(); - chdir(dir); - const git = simpleGit(dir) - await git.init(); - await git.add('-A'); - await git.commit('init test theme'); - chdir(originalDir); - } - const testThemesDir = path.resolve(__dirname, '../test-themes'); - const testThemes = fs.readdirSync(testThemesDir); - for (const themeName of testThemes) { - await initGitRepo(path.resolve(testThemesDir, themeName)); - } -} - -/** - * Transform all theme folders under test-themes back into regular folders, - * so they can be tracked properly by jambo's git repo. - */ -function cleanupTestThemes() { - const testThemesDir = path.resolve(__dirname, '../test-themes'); - const testThemes = fs.readdirSync(testThemesDir); - testThemes.forEach(themeName => { - const themeGitFolder = path.resolve(testThemesDir, themeName, '.git'); - fsExtra.removeSync(themeGitFolder); - }); -} /** * Runs a test suite in a playground-[id] folder. * * @param {Function} testFunction */ -exports.runInPlayground = async function(testFunction) { +exports.runInPlayground = async function(testFunction, procrastinateCleanup = false) { const originalDir = cwd(); const id = parseInt(Math.random() * 99999999); const playgroundDir = path.resolve(__dirname, '../playground-' + id ) async function setup() { fsExtra.mkdirpSync(playgroundDir); - await setupTestThemes(); chdir(playgroundDir); } function cleanup() { chdir(originalDir); - cleanupTestThemes(); - fsExtra.removeSync(playgroundDir); + if (!procrastinateCleanup) { + fsExtra.removeSync(playgroundDir); + } } try { diff --git a/tests/acceptance/suites/basic-flow.js b/tests/acceptance/suites/basic-flow.js index 3d864760..f9b9b9ea 100644 --- a/tests/acceptance/suites/basic-flow.js +++ b/tests/acceptance/suites/basic-flow.js @@ -2,9 +2,6 @@ const fs = require('fs'); const { runInPlayground } = require('../setup/playground'); -// silence jambo's noisy output -console.log = jest.fn(); - it('can init -> import -> create page -> build', () => runInPlayground(async t => { await t.jambo('init'); await t.jambo('import --themeUrl ../test-themes/basic-flow'); diff --git a/tests/acceptance/suites/describe.js b/tests/acceptance/suites/describe.js index b3aa4119..ae91eee8 100644 --- a/tests/acceptance/suites/describe.js +++ b/tests/acceptance/suites/describe.js @@ -8,7 +8,8 @@ it('describe on default and custom commands', () => runInPlayground(async t => { await t.jambo('init'); await t.jambo('import --themeUrl ../test-themes/describe'); await t.jambo('describe'); + const receivedJSON = JSON.parse(console.log.mock.calls[0][0]); const expectedJSON = - fs.readFileSync('../fixtures/describe/describe-test.json', 'utf-8'); - expect(console.log).toHaveBeenCalledWith(expectedJSON); + JSON.parse(fs.readFileSync('../fixtures/describe/describe-test.json', 'utf-8')); + expect(receivedJSON).toEqual(expectedJSON); })); \ No newline at end of file diff --git a/tests/setup/globalsetup.js b/tests/setup/globalsetup.js new file mode 100644 index 00000000..8134cc98 --- /dev/null +++ b/tests/setup/globalsetup.js @@ -0,0 +1,5 @@ +const { setupTestThemes } = require('./themesetup'); + +module.exports = async () => { + await setupTestThemes(); +} diff --git a/tests/setup/globalteardown.js b/tests/setup/globalteardown.js new file mode 100644 index 00000000..16fe146f --- /dev/null +++ b/tests/setup/globalteardown.js @@ -0,0 +1,6 @@ +const { cleanupTestThemes } = require('./themesetup'); + +module.exports = async () => { + cleanupTestThemes(); +} + diff --git a/tests/setup/setup.js b/tests/setup/setup.js index 15eeedc4..8bb09868 100644 --- a/tests/setup/setup.js +++ b/tests/setup/setup.js @@ -1,6 +1,9 @@ const prettier = require('prettier'); +const { setLogLevel } = require('../../src/utils/logger'); process.env.IS_DEVELOPMENT_PREVIEW = 'false'; +setLogLevel('silent'); + expect.extend({ toEqualHtml: function(received, expected) { const normalize = html => { @@ -19,4 +22,4 @@ expect.extend({ }; } } -}); +}); \ No newline at end of file diff --git a/tests/setup/themesetup.js b/tests/setup/themesetup.js new file mode 100644 index 00000000..1140d961 --- /dev/null +++ b/tests/setup/themesetup.js @@ -0,0 +1,38 @@ +const fs = require('fs'); +const fsExtra = require('fs-extra'); +const path = require('path'); +const { chdir, cwd } = require('process'); +const simpleGit = require('simple-git/promise'); + +const testThemesDir = path.resolve(__dirname, '../acceptance/test-themes'); + +/** + * Transform all theme folders under test-themes/ into git repos. + */ +exports.setupTestThemes = async function() { + async function initGitRepo(dir) { + const originalDir = cwd(); + chdir(dir); + const git = simpleGit(dir) + await git.init(); + await git.add('-A'); + await git.commit('init test theme'); + chdir(originalDir); + } + const testThemes = fs.readdirSync(testThemesDir); + for (const themeName of testThemes) { + await initGitRepo(path.resolve(testThemesDir, themeName)); + } +} + +/** + * Transform all theme folders under test-themes back into regular folders, + * so they can be tracked properly by jambo's git repo. + */ +exports.cleanupTestThemes = function() { + const testThemes = fs.readdirSync(testThemesDir); + testThemes.forEach(themeName => { + const themeGitFolder = path.resolve(testThemesDir, themeName, '.git'); + fsExtra.removeSync(themeGitFolder); + }); +} \ No newline at end of file From dfc7e3d5620a3a4ff00313e51367d0dcc41fda11 Mon Sep 17 00:00:00 2001 From: cjiang2000 <44913744+cjiang2000@users.noreply.github.com> Date: Fri, 25 Jun 2021 14:03:06 -0400 Subject: [PATCH 07/13] Dev/customcommand (#234) * Added custom command acceptance test. J=1304 TEST=auto * fixed spacing * added word 'file' to comment * removed console.log = jest.fn(); --- .../customcommand/customcommand-test.html | 1 + tests/acceptance/suites/customcommand.js | 14 ++++++ tests/acceptance/suites/describe.js | 2 +- .../commands/addVertical.js | 49 +++++++++++++------ .../{describe => customcommand}/postimport.js | 2 +- .../describe-template/page-config.json | 0 .../templates/describe-template/page.html.hbs | 0 7 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 tests/acceptance/fixtures/customcommand/customcommand-test.html create mode 100644 tests/acceptance/suites/customcommand.js rename tests/acceptance/test-themes/{describe => customcommand}/commands/addVertical.js (58%) rename tests/acceptance/test-themes/{describe => customcommand}/postimport.js (93%) rename tests/acceptance/test-themes/{describe => customcommand}/templates/describe-template/page-config.json (100%) rename tests/acceptance/test-themes/{describe => customcommand}/templates/describe-template/page.html.hbs (100%) diff --git a/tests/acceptance/fixtures/customcommand/customcommand-test.html b/tests/acceptance/fixtures/customcommand/customcommand-test.html new file mode 100644 index 00000000..6308731a --- /dev/null +++ b/tests/acceptance/fixtures/customcommand/customcommand-test.html @@ -0,0 +1 @@ +testvertuniversal-standardtestkey \ No newline at end of file diff --git a/tests/acceptance/suites/customcommand.js b/tests/acceptance/suites/customcommand.js new file mode 100644 index 00000000..894a7601 --- /dev/null +++ b/tests/acceptance/suites/customcommand.js @@ -0,0 +1,14 @@ +const fs = require('fs'); + +const { runInPlayground } = require('../setup/playground'); + +it('import and execute custom commands', () => runInPlayground(async t => { + await t.jambo('init'); + await t.jambo('import --themeUrl ../test-themes/customcommand'); + await t.jambo( + 'vertical --name testvert --verticalKey testkey --template universal-standard'); + const actualPage = fs.readFileSync('index.html', 'utf-8'); + const expectedPage = fs.readFileSync( + '../fixtures/customcommand/customcommand-test.html', 'utf-8'); + expect(actualPage).toEqualHtml(expectedPage); +})); \ No newline at end of file diff --git a/tests/acceptance/suites/describe.js b/tests/acceptance/suites/describe.js index ae91eee8..72007562 100644 --- a/tests/acceptance/suites/describe.js +++ b/tests/acceptance/suites/describe.js @@ -6,7 +6,7 @@ console.log = jest.fn(); it('describe on default and custom commands', () => runInPlayground(async t => { await t.jambo('init'); - await t.jambo('import --themeUrl ../test-themes/describe'); + await t.jambo('import --themeUrl ../test-themes/customcommand'); await t.jambo('describe'); const receivedJSON = JSON.parse(console.log.mock.calls[0][0]); const expectedJSON = diff --git a/tests/acceptance/test-themes/describe/commands/addVertical.js b/tests/acceptance/test-themes/customcommand/commands/addVertical.js similarity index 58% rename from tests/acceptance/test-themes/describe/commands/addVertical.js rename to tests/acceptance/test-themes/customcommand/commands/addVertical.js index 58f9aaab..d6bb8218 100644 --- a/tests/acceptance/test-themes/describe/commands/addVertical.js +++ b/tests/acceptance/test-themes/customcommand/commands/addVertical.js @@ -29,19 +29,28 @@ class VerticalAdder { */ static args() { return { - name: new ArgumentMetadata( - ArgumentType.STRING, 'name of the vertical\'s page', true), - verticalKey: new ArgumentMetadata(ArgumentType.STRING, 'the vertical\'s key', true), - cardName: new ArgumentMetadata( - ArgumentType.STRING, 'card to use with vertical', false), - template: new ArgumentMetadata( - ArgumentType.STRING, 'page template to use within theme', true), - locales: new ArgumentMetadata( - ArgumentType.ARRAY, - 'additional locales to generate the page for', - false, - [], - ArgumentType.STRING) + name: new ArgumentMetadata({ + itemType: ArgumentType.STRING, + description: 'name of the vertical\'s page', + isRequired: true}), + verticalKey: new ArgumentMetadata({ + itemType: ArgumentType.STRING, + description: 'the vertical\'s key', + isRequired: true}), + cardName: new ArgumentMetadata({ + itemType: ArgumentType.STRING, + description: 'card to use with vertical', + isRequired: false}), + template: new ArgumentMetadata({ + itemType: ArgumentType.STRING, + description: 'page template to use within theme', + isRequired: true}), + locales: new ArgumentMetadata({ + type: ArgumentType.ARRAY, + description: 'additional locales to generate the page for', + isRequired: false, + defaultValue: [], + itemType: ArgumentType.STRING}) }; } @@ -80,10 +89,20 @@ class VerticalAdder { } /** - * Executes the add vertical command with the provided arguments. + * Executes a command that creates an html file. * * @param {Object} args The arguments, keyed by name */ - execute(args) {} + execute(args) { + const fs = require('fs'); + const content = args.name + args.template + args.verticalKey; + fs.writeFileSync('index.html', content, err => { + if (err) { + console.error(err); + return; + } + //file written successfully + }) + } } module.exports = VerticalAdder; \ No newline at end of file diff --git a/tests/acceptance/test-themes/describe/postimport.js b/tests/acceptance/test-themes/customcommand/postimport.js similarity index 93% rename from tests/acceptance/test-themes/describe/postimport.js rename to tests/acceptance/test-themes/customcommand/postimport.js index 21967e3f..0849f4a3 100755 --- a/tests/acceptance/test-themes/describe/postimport.js +++ b/tests/acceptance/test-themes/customcommand/postimport.js @@ -15,4 +15,4 @@ function updateDefaultTheme(themeName) { } } -updateDefaultTheme('describe'); \ No newline at end of file +updateDefaultTheme('customcommand'); \ No newline at end of file diff --git a/tests/acceptance/test-themes/describe/templates/describe-template/page-config.json b/tests/acceptance/test-themes/customcommand/templates/describe-template/page-config.json similarity index 100% rename from tests/acceptance/test-themes/describe/templates/describe-template/page-config.json rename to tests/acceptance/test-themes/customcommand/templates/describe-template/page-config.json diff --git a/tests/acceptance/test-themes/describe/templates/describe-template/page.html.hbs b/tests/acceptance/test-themes/customcommand/templates/describe-template/page.html.hbs similarity index 100% rename from tests/acceptance/test-themes/describe/templates/describe-template/page.html.hbs rename to tests/acceptance/test-themes/customcommand/templates/describe-template/page.html.hbs From b2bce08cc90f6d603021d1a4c458a5d2d5f465a9 Mon Sep 17 00:00:00 2001 From: Oliver Shi Date: Mon, 28 Jun 2021 13:24:05 -0400 Subject: [PATCH 08/13] add --globs param to extract-translations cmd (#233) This commit updates the extract-translations command to take in a --globs argument. The TranslationExtractor was refactored to only take in a glob, instead of taking in arrays of files, directories, and ignored files. The logic of creating an array of globs by reading from the jambo.json and gitignore files has been moved into the DefaultTranslationGlobber. simple-git was updated from 1.131 to 2.40 to remove git ENOENT errors during acceptance tests. There were improvements to how simple-git performs git operations in parallel after v2. We only use simple-git for very basic operations like cloning, init-ing, and checking out submodules, staging files, and committing, so there shouldn't be any issues with upgrading. The only breaking changes in v2.0.0 are git.log using strict ISO by default, and changes to methods that being with underscores (aka private methods). J=SLAP-1378 TEST=manual,auto run jambo extract-translations in the theme, and also in campbells-ca see messages.pot generated as expected add acceptance + unit tests --- package-lock.json | 33 +++++++-- package.json | 2 +- .../defaulttranslationglobber.js | 61 ++++++++++++++++ .../jambotranslationextractor.js | 64 +++++++---------- src/i18n/extractor/translationextractor.js | 46 +++++------- src/utils/gitutils.js | 15 ++++ .../fixtures/describe/describe-test.json | 5 ++ .../extract-translations/custom-globs.pot | 13 ++++ .../fixtures/extract-translations/default.pot | 17 +++++ .../acceptance/suites/extract-translations.js | 33 +++++++++ .../basic-flow/translations/lonewolf.hbs | 1 + .../translation-folder/ignored.hbs | 1 + .../translation-folder/template.hbs | 13 ++++ .../i18n/extract/defaulttranslationglobber.js | 71 +++++++++++++++++++ tests/i18n/extract/translationextractor.js | 33 +++------ 15 files changed, 311 insertions(+), 97 deletions(-) create mode 100644 src/commands/extract-translations/defaulttranslationglobber.js create mode 100644 tests/acceptance/fixtures/extract-translations/custom-globs.pot create mode 100644 tests/acceptance/fixtures/extract-translations/default.pot create mode 100644 tests/acceptance/suites/extract-translations.js create mode 100644 tests/acceptance/test-themes/basic-flow/translations/lonewolf.hbs create mode 100644 tests/acceptance/test-themes/basic-flow/translations/translation-folder/ignored.hbs create mode 100644 tests/acceptance/test-themes/basic-flow/translations/translation-folder/template.hbs create mode 100644 tests/i18n/extract/defaulttranslationglobber.js diff --git a/package-lock.json b/package-lock.json index e0142a1a..1f16107b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3461,6 +3461,19 @@ } } }, + "@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "requires": { + "debug": "^4.1.1" + } + }, + "@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -8285,11 +8298,23 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "simple-git": { - "version": "1.132.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.132.0.tgz", - "integrity": "sha512-xauHm1YqCTom1sC9eOjfq3/9RKiUA9iPnxBbrY2DdL8l4ADMu0jjM5l5lphQP5YWNqAL2aXC/OeuQ76vHtW5fg==", + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.40.0.tgz", + "integrity": "sha512-7IO/eQwrN5kvS38TTu9ljhG9tx2nn0BTqZOmqpPpp51TvE44YIvLA6fETqEVA8w/SeEfPaVv6mk7Tsk9Jns+ag==", "requires": { - "debug": "^4.0.1" + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + } } }, "sisteransi": { diff --git a/package.json b/package.json index 5b0c59b3..4fadb115 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "merge-options": "^2.0.0", "npmlog": "^4.1.2", "prompts": "^2.3.1", - "simple-git": "^1.131.0", + "simple-git": "^2.40.0", "yargs": "^17.0.0" }, "devDependencies": { diff --git a/src/commands/extract-translations/defaulttranslationglobber.js b/src/commands/extract-translations/defaulttranslationglobber.js new file mode 100644 index 00000000..8848f594 --- /dev/null +++ b/src/commands/extract-translations/defaulttranslationglobber.js @@ -0,0 +1,61 @@ +const fs = require('fs'); + +/** + * DefaultTranslationGlobber contains the default logic for determining + * which files are scanned by the extract-translations command. + */ +module.exports = class DefaultTranslationGlobber { + constructor(dirs = {}, ignoredPaths = []) { + this.pages = dirs.pages || ''; + this.partials = dirs.partials || []; + this.ignoredPaths = ignoredPaths; + this.extensions = ['.js', '.hbs']; // only extract from files with these extensions + } + + /** + * Returns the default globs to be scanned, using the given + * site specific templates and partials, as well as any ignored paths. + * + * @returns {Array} + */ + getGlobs() { + const { files, directories } = this._getFilesAndDirsFromJamboConfig(); + return this._globInputFilePaths(directories, files, this.ignoredPaths); + } + + /** + * Globs together the given directories, files, and ignored paths. + * + * @param {Array} directories directories to recursively extract from + * @param {Array} files any individual files to extract from + * @param {Array} ignoredPaths paths to recursively ignore + * @returns {Array} + */ + _globInputFilePaths(directories, files, ignoredPaths) { + const extensions = this.extensions.join(','); + const directoryGlobs = directories.map(dirpath => `${dirpath}/**/*{${extensions}}`); + const ignoreGlobs = ignoredPaths.map(dirpath => `!${dirpath}`); + return [...directoryGlobs, ...files, ...ignoreGlobs]; + } + + /** + * Returns the site-specific partials/templates in a jambo config, + * separating them based on whether they are files or directories. + * + * @returns {{files: Array., directories: Array.}} + */ + _getFilesAndDirsFromJamboConfig() { + const files = []; + const directories = []; + const pathsThatExist = [this.pages, ...this.partials].filter(p => fs.existsSync(p)); + for (const pathname of pathsThatExist) { + const isFile = fs.lstatSync(pathname).isFile(); + if (isFile) { + files.push(pathname); + } else { + directories.push(pathname); + } + } + return { files: files, directories: directories }; + } +} \ No newline at end of file diff --git a/src/commands/extract-translations/jambotranslationextractor.js b/src/commands/extract-translations/jambotranslationextractor.js index 184e39e0..b4eecd91 100644 --- a/src/commands/extract-translations/jambotranslationextractor.js +++ b/src/commands/extract-translations/jambotranslationextractor.js @@ -1,15 +1,15 @@ const TranslationExtractor = require('../../i18n/extractor/translationextractor'); const { ArgumentMetadata, ArgumentType } = require('../../models/commands/argumentmetadata'); -const fsExtra = require('fs-extra'); -const fs = require('fs'); const { info } = require('../../utils/logger'); +const DefaultTranslationGlobber = require('./defaulttranslationglobber'); +const { readGitignorePaths } = require('../../utils/gitutils'); /** * JamboTranslationExtractor extracts translations from a jambo repo. */ class JamboTranslationExtractor { constructor(jamboConfig) { - this.config = jamboConfig; + this.jamboConfig = jamboConfig; this.extractor = new TranslationExtractor(); } @@ -23,6 +23,13 @@ class JamboTranslationExtractor { static args() { return { + globs: new ArgumentMetadata({ + displayName: 'Globs to Scan', + type: ArgumentType.ARRAY, + description: + 'specify globs to scan for translations, instead of using the defaults', + isRequired: false + }), output: new ArgumentMetadata({ displayName: 'Output Path', type: ArgumentType.STRING, @@ -44,52 +51,29 @@ class JamboTranslationExtractor { }; } - execute(args) { - this._extract(args.output); + execute({ output, globs }) { + if (globs && globs.length > 0) { + this._extract(output, globs); + } else { + const gitignorePaths = readGitignorePaths(); + const defaultGlobber = new DefaultTranslationGlobber( + this.jamboConfig.dirs, gitignorePaths); + const defaultGlobs = defaultGlobber.getGlobs(); + this._extract(output, defaultGlobs); + } } /** * Extracts i18n strings from a jambo repo to a designed output file. + * * @param {string} outputPath + * @param {Array} globs */ - async _extract(outputPath) { - const { files, directories } = this._getFilesAndDirsFromJamboConfig(); - const gitignorePaths = this._parseGitignorePaths(); + _extract(outputPath, globs) { info(`Extracting translations to ${outputPath}`); - this.extractor.extract({ - specificFiles: files, - directories: directories, - ignoredPaths: gitignorePaths - }); + this.extractor.extract(globs); this.extractor.savePotFile(outputPath); } - - /** - * Gets the list of gitignored paths, if a .gitignore file exists. - * @returns {Promise.>} - */ - _parseGitignorePaths() { - if (fsExtra.pathExistsSync('.gitignore')) { - const ignoredPaths = fs.readFileSync('.gitignore', 'utf-8'); - return ignoredPaths.split('\n').filter(pathname => pathname); - } - return []; - } - - /** - * Returns an array of files and array of directories contained in the jamboConfig. - * @returns {{files: Array., directories: Array.}} - */ - _getFilesAndDirsFromJamboConfig() { - const { pages, partials } = this.config.dirs; - const files = []; - const directories = []; - for (const pathname of [pages, ...partials]) { - const isFile = fs.existsSync(pathname) && fs.lstatSync(pathname).isFile(); - isFile ? files.push(pathname) : directories.push(pathname); - } - return { files: files, directories: directories }; - } } module.exports = JamboTranslationExtractor; diff --git a/src/i18n/extractor/translationextractor.js b/src/i18n/extractor/translationextractor.js index 8e386181..89e026ca 100755 --- a/src/i18n/extractor/translationextractor.js +++ b/src/i18n/extractor/translationextractor.js @@ -5,6 +5,7 @@ const TranslateInvocation = require('../../handlebars/models/translateinvocation const fs = require('fs'); const path = require('path'); const fsExtra = require('fs-extra'); +const { error } = require('../../utils/logger'); /** * TranslationExtractor is a class that extracts handlebars translation invocations @@ -13,7 +14,6 @@ const fsExtra = require('fs-extra'); class TranslationExtractor { constructor(options) { this._options = { - extensions: ['.hbs', '.js'], // only extract from files with these extensions translateMethods: [ 'translate', 'translateJS' ], // method names to search for baseDirectory: process.cwd(), // the root directory when adding a reference to // the filepath:linenumber of a translation @@ -24,15 +24,13 @@ class TranslationExtractor { /** * Extracts messages from all of the input files into the extractor. - * @param {Object} input - * @param {Array} input.directories directories to recursively extract from - * @param {Array} input.specificFiles specific files to extract from - * @param {Array} input.ignoredPaths paths to recursively ignore + * + * @param {Array} globs */ - extract(input) { - const { directories, specificFiles, ignoredPaths} = input; - const filepaths = this._globInputFilePaths( - directories || [], specificFiles || [], ignoredPaths || []); + extract(globs) { + const filepaths = globby.sync(globs).map(fp => { + return path.resolve(this._options.baseDirectory, fp) + }); for (const filepath of filepaths) { const template = fs.readFileSync(filepath).toString(); const filepathForReference = path.relative(this._options.baseDirectory, filepath); @@ -60,27 +58,17 @@ class TranslationExtractor { this._extractor.savePotFile(outputPath); } - /** - * Globs together an array of files to extract from. - * @param {Array} directories directories to recursively extract from - * @param {Array} specificFiles specific files to extract from - * @param {Array} ignoredPaths paths to recursively ignore - * @returns {Array} - */ - _globInputFilePaths(directories, specificFiles, ignoredPaths) { - const extensions = this._options.extensions.join(','); - const directoryGlobs = directories.map(dirpath => `${dirpath}/**/*{${extensions}}`); - const ignoreGlobs = ignoredPaths.map(dirpath => `!${dirpath}`); - const files = globby.sync([...directoryGlobs, ...specificFiles, ...ignoreGlobs]); - return files; - } - _extractMessagesFromTemplate(template, filepath) { - const tree = Handlebars.parseWithoutProcessing(template); - const visitor = new Handlebars.Visitor(); - visitor.MustacheStatement = - mustacheStatement => this._handleMustacheStatement(mustacheStatement, filepath); - visitor.accept(tree); + try { + const tree = Handlebars.parseWithoutProcessing(template); + const visitor = new Handlebars.Visitor(); + visitor.MustacheStatement = + mustacheStatement => this._handleMustacheStatement(mustacheStatement, filepath); + visitor.accept(tree); + } catch (err) { + error(`Unable to extract translations from ${filepath}`) + error(err.message); + } } _handleMustacheStatement(mustacheStatement, filepath) { diff --git a/src/utils/gitutils.js b/src/utils/gitutils.js index 400e3a22..5df740a4 100644 --- a/src/utils/gitutils.js +++ b/src/utils/gitutils.js @@ -1,4 +1,6 @@ const path = require('path'); +const fs = require('fs'); +const fsExtra = require('fs-extra'); /** * Gets the repo name from a git repo URL @@ -7,3 +9,16 @@ const path = require('path'); exports.getRepoNameFromURL = function(repoURL) { return path.basename(repoURL, '.git'); } + +/** + * Reads a gitignore. + * + * @return {Array} + */ +exports.readGitignorePaths = function() { + if (fsExtra.pathExistsSync('.gitignore')) { + const ignoredPaths = fs.readFileSync('.gitignore', 'utf-8'); + return ignoredPaths.split('\n').filter(pathname => pathname); + } + return []; +} diff --git a/tests/acceptance/fixtures/describe/describe-test.json b/tests/acceptance/fixtures/describe/describe-test.json index 6d403826..b74bcace 100644 --- a/tests/acceptance/fixtures/describe/describe-test.json +++ b/tests/acceptance/fixtures/describe/describe-test.json @@ -83,6 +83,11 @@ "extract-translations": { "displayName": "Extract Translations", "params": { + "globs": { + "displayName": "Globs to Scan", + "required": false, + "type": "array" + }, "output": { "displayName": "Output Path", "type": "string", diff --git a/tests/acceptance/fixtures/extract-translations/custom-globs.pot b/tests/acceptance/fixtures/extract-translations/custom-globs.pot new file mode 100644 index 00000000..b124634b --- /dev/null +++ b/tests/acceptance/fixtures/extract-translations/custom-globs.pot @@ -0,0 +1,13 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" + +#: themes/basic-flow/translations/translation-folder/ignored.hbs:1 +#: translations/translation-folder/ignored.hbs:1 +msgid "ignore me by default!" +msgstr "" + +#: user-created.js:1 +msgctxt "RSVP is a verb" +msgid "RSVP" +msgstr "" diff --git a/tests/acceptance/fixtures/extract-translations/default.pot b/tests/acceptance/fixtures/extract-translations/default.pot new file mode 100644 index 00000000..ef6ea116 --- /dev/null +++ b/tests/acceptance/fixtures/extract-translations/default.pot @@ -0,0 +1,17 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" + +#: translations/lonewolf.hbs:1 +msgid "don't forget me by default though!" +msgstr "" + +#: translations/translation-folder/template.hbs:1 +msgctxt "Example: 1-10 of 50" +msgid "" +"[[start]]\n" +"-\n" +"[[end]]\n" +"of\n" +"[[resultsCount]]" +msgstr "" diff --git a/tests/acceptance/suites/extract-translations.js b/tests/acceptance/suites/extract-translations.js new file mode 100644 index 00000000..0ea5627a --- /dev/null +++ b/tests/acceptance/suites/extract-translations.js @@ -0,0 +1,33 @@ +const fs = require('fs'); + +const { runInPlayground } = require('../setup/playground'); + +it('extract translations w/ default settings', () => runInPlayground(async t => { + await t.jambo('init'); + await t.jambo('import --themeUrl ../test-themes/basic-flow'); + await t.jambo('override --path translations/translation-folder'); + await t.jambo('override --path translations/lonewolf.hbs'); + fs.writeFileSync('.gitignore', '**/ignored.hbs') + await t.jambo('extract-translations'); + const expectedPot = fs.readFileSync('messages.pot', 'utf-8'); + const actualPot = fs.readFileSync( + '../fixtures/extract-translations/default.pot', 'utf-8'); + expect(actualPot).toEqual(expectedPot); +})); + +it('extract translations w/ custom globs', () => runInPlayground(async t => { + await t.jambo('init'); + await t.jambo('import --themeUrl ../test-themes/basic-flow'); + await t.jambo('override --path translations/translation-folder'); + await t.jambo('override --path translations/lonewolf.hbs'); + fs.writeFileSync('.gitignore', '**/ignored.hbs') + await fs.writeFileSync( + 'user-created.js', + '{{ translateJS phrase=\'RSVP\' context=\'RSVP is a verb\' }}' + ); + await t.jambo('extract-translations --globs \'**/ignored.hbs\' user-created.js'); + const expectedPot = fs.readFileSync('messages.pot', 'utf-8'); + const actualPot = fs.readFileSync( + '../fixtures/extract-translations/custom-globs.pot', 'utf-8'); + expect(actualPot).toEqual(expectedPot); +})); \ No newline at end of file diff --git a/tests/acceptance/test-themes/basic-flow/translations/lonewolf.hbs b/tests/acceptance/test-themes/basic-flow/translations/lonewolf.hbs new file mode 100644 index 00000000..c2ce8f70 --- /dev/null +++ b/tests/acceptance/test-themes/basic-flow/translations/lonewolf.hbs @@ -0,0 +1 @@ +{{translate phrase='don\'t forget me by default though!'}} \ No newline at end of file diff --git a/tests/acceptance/test-themes/basic-flow/translations/translation-folder/ignored.hbs b/tests/acceptance/test-themes/basic-flow/translations/translation-folder/ignored.hbs new file mode 100644 index 00000000..a01a1175 --- /dev/null +++ b/tests/acceptance/test-themes/basic-flow/translations/translation-folder/ignored.hbs @@ -0,0 +1 @@ +{{translate phrase='ignore me by default!'}} \ No newline at end of file diff --git a/tests/acceptance/test-themes/basic-flow/translations/translation-folder/template.hbs b/tests/acceptance/test-themes/basic-flow/translations/translation-folder/template.hbs new file mode 100644 index 00000000..a564e281 --- /dev/null +++ b/tests/acceptance/test-themes/basic-flow/translations/translation-folder/template.hbs @@ -0,0 +1,13 @@ +{{translate + phrase= +'[[start]] +- +[[end]] +of +[[resultsCount]]' + context='Example: 1-10 of 50' + start=pageStart + end=pageEnd + resultsCount=total + escapeHTML='false' +}} \ No newline at end of file diff --git a/tests/i18n/extract/defaulttranslationglobber.js b/tests/i18n/extract/defaulttranslationglobber.js new file mode 100644 index 00000000..7eca55ea --- /dev/null +++ b/tests/i18n/extract/defaulttranslationglobber.js @@ -0,0 +1,71 @@ +const path = require('path'); +const process = require('process'); + +const DefaultTranslationGlobber = require('../../../src/commands/extract-translations/defaulttranslationglobber'); + +const fixturesAbsPath = path.resolve(__dirname, '../../fixtures/extractions'); +const fixturesDir = path.relative(process.cwd(), fixturesAbsPath); + +it('can glob a single file', () => { + const filePath = path.join(fixturesDir, 'rawcomponent.js'); + const globber = new DefaultTranslationGlobber({ + partials: [ filePath ] + }); + expect(globber.getGlobs()).toEqual([ filePath ]); +}); + +it('will ignore paths that do not exist', () => { + const globber = new DefaultTranslationGlobber({ + partials: [ 'DNE.txt' ] + }); + expect(globber.getGlobs()).toEqual([]); +}); + +it('will glob partial folder paths', () => { + const globber = new DefaultTranslationGlobber({ + partials: [ fixturesDir ] + }); + expect(globber.getGlobs()).toEqual([`${fixturesDir}/**/*{.js,.hbs}`]); +}); + +it('will glob the pages folder', () => { + const globber = new DefaultTranslationGlobber({ + pages: fixturesDir + }); + expect(globber.getGlobs()).toEqual([`${fixturesDir}/**/*{.js,.hbs}`]); +}); + +it('will ignore a pages folder that does not exist', () => { + const globber = new DefaultTranslationGlobber({ + pages: 'DNE/DNE/DNE' + }); + expect(globber.getGlobs()).toEqual([]); +}); + +it('ignores paths properly', () => { + const ignoredPaths = ['node_modules', 'static/node_modules']; + const globber = new DefaultTranslationGlobber({}, ignoredPaths); + expect(globber.getGlobs()).toEqual([ + '!node_modules', + '!static/node_modules', + ]); +}); + +it('can handle input w/pages + partials + gitignore', () => { + const ignoredPaths = ['node_modules', 'static/node_modules']; + const componentPath = path.join(fixturesDir, 'rawcomponent.js'); + const templatePath = path.join(fixturesDir, 'rawtemplate.hbs'); + const fakePath = 'DNE/DNE'; + const globber = new DefaultTranslationGlobber({ + pages: fixturesDir, + partials: [ fixturesDir, componentPath, templatePath, fakePath ] + }, ignoredPaths); + expect(globber.getGlobs()).toEqual([ + 'tests/fixtures/extractions/**/*{.js,.hbs}', + 'tests/fixtures/extractions/**/*{.js,.hbs}', + 'tests/fixtures/extractions/rawcomponent.js', + 'tests/fixtures/extractions/rawtemplate.hbs', + '!node_modules', + '!static/node_modules', + ]); +}); diff --git a/tests/i18n/extract/translationextractor.js b/tests/i18n/extract/translationextractor.js index a2795287..33caa42c 100644 --- a/tests/i18n/extract/translationextractor.js +++ b/tests/i18n/extract/translationextractor.js @@ -2,9 +2,11 @@ const TranslationExtractor = require('../../../src/i18n/extractor/translationext const path = require('path'); const fs = require('fs'); - describe('TranslationExtractor', () => { - const fixturesDir = path.resolve(__dirname, '../../fixtures/extractions'); + const rootDir = path.resolve(__dirname, '../../..'); + const fixturesAbsPath = path.resolve(__dirname, '../../fixtures/extractions'); + const fixturesDir = path.relative(rootDir, fixturesAbsPath); + const rawcomponentPot = readExpectedPot('rawcomponent.pot'); const combinedPot = readExpectedPot('combined.pot'); const rawtemplatePot = readExpectedPot('rawtemplate.pot'); @@ -24,9 +26,7 @@ describe('TranslationExtractor', () => { it('can extract from just an hbs file', () => { const rawtemplate = path.relative(process.cwd(), path.join(fixturesDir, 'rawtemplate.hbs')); - extractor.extract({ - specificFiles: [ rawtemplate ] - }); + extractor.extract([ rawtemplate ]); const potString = extractor.getPotString(); expect(potString).toEqual(rawtemplatePot); }); @@ -34,36 +34,26 @@ describe('TranslationExtractor', () => { it('can extract from just a js file', () => { const rawcomponent = path.relative(process.cwd(), path.join(fixturesDir, 'rawcomponent.js')); - extractor.extract({ - specificFiles: [ rawcomponent ] - }); + extractor.extract([ rawcomponent ]); const potString = extractor.getPotString(); expect(potString).toEqual(rawcomponentPot); }); it('can extract all translations from a folder', () => { - extractor.extract({ - directories: [ fixturesDir ] - }); + extractor.extract([ fixturesDir ]); const potString = extractor.getPotString(); expect(potString).toEqual(combinedPot); }); it('can ignore an entire folder', async () => { - extractor.extract({ - directories: [ fixturesDir ], - ignoredPaths: [ fixturesDir ] - }); + extractor.extract([ fixturesDir, `!${fixturesDir}` ]); const potString = extractor.getPotString(); expect(potString).toEqual(emptyPot); }); it('can ignore a specific file when specifying a directory', () => { const rawcomponent = path.join(fixturesDir, 'rawcomponent.js'); - extractor.extract({ - directories: [ fixturesDir ], - ignoredPaths: [ rawcomponent ] - }); + extractor.extract([ fixturesDir, `!${rawcomponent}` ]); const potString = extractor.getPotString(); expect(potString).toEqual(rawtemplatePot); }); @@ -71,10 +61,7 @@ describe('TranslationExtractor', () => { it('ignoring a specific file takes priority over specifying that file', async () => { const rawcomponent = path.relative(process.cwd(), path.join(fixturesDir, 'rawcomponent.js')); - extractor.extract({ - specificFiles: [ rawcomponent ], - ignoredPaths: [ rawcomponent ] - }); + extractor.extract([ rawcomponent, `!${rawcomponent}` ]); const potString = extractor.getPotString(); expect(potString).toEqual(emptyPot); }); From 501ed8bbe91ee38fcbac837a814ce2359403961e Mon Sep 17 00:00:00 2001 From: cjiang2000 <44913744+cjiang2000@users.noreply.github.com> Date: Tue, 29 Jun 2021 11:50:06 -0400 Subject: [PATCH 09/13] Changed upgrade functionality (#235) Changed upgrade functionality, so that now it reverts to old theme files when it errors out. J=SLAP-1308 TEST=manual/automatic Tested by running jambo upgrade on the hitchhiker theme and making sure that upgrade ran unchanged when no error occurs. Also ran with an artificial error to make sure that theme files are reverted correctly. Created acceptance test for themeupgrader that checks to make sure that when jambo upgrade errors out, the test-theme remains unchanged. --- src/commands/upgrade/themeupgrader.js | 21 ++++++++++++------ src/utils/thememanager.js | 2 +- tests/acceptance/suites/themeupgrader.js | 28 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 tests/acceptance/suites/themeupgrader.js diff --git a/src/commands/upgrade/themeupgrader.js b/src/commands/upgrade/themeupgrader.js index 123a0524..71206965 100644 --- a/src/commands/upgrade/themeupgrader.js +++ b/src/commands/upgrade/themeupgrader.js @@ -1,7 +1,6 @@ const fs = require('fs-extra'); const path = require('path'); const simpleGit = require('simple-git/promise'); - const ThemeManager = require('../../utils/thememanager'); const { CustomCommand } = require('../../utils/customcommands/command'); const { CustomCommandExecuter } = require('../../utils/customcommands/commandexecuter'); @@ -77,8 +76,8 @@ class ThemeUpgrader { } } - execute(args) { - this._upgrade({ + async execute(args) { + await this._upgrade({ themeName: this.jamboConfig.defaultTheme, disableScript: args.disableScript, isLegacy: args.isLegacy, @@ -104,12 +103,20 @@ class ThemeUpgrader { throw new UserError( `Theme "${themeName}" not found within the "${this._themesDir}" folder`); } - - if (await this._isGitSubmodule(themePath)) { + if (await this._isGitSubmodule(themePath)) { await this._upgradeSubmodule(themePath, branch) } else { - await this._recloneTheme(themeName, themePath, branch); - this._removeGitFolder(themePath); + const tempDir = fs.mkdtempSync('./'); + try { + fs.copySync(themePath, tempDir); + await this._recloneTheme(themeName, themePath, branch); + this._removeGitFolder(themePath); + fs.removeSync(tempDir); + } + catch (error) { + fs.moveSync(tempDir, themePath); + throw error; + } } if (!disableScript) { this._executePostUpgradeScript(themePath, isLegacy); diff --git a/src/utils/thememanager.js b/src/utils/thememanager.js index 87e227c8..aa5acea7 100644 --- a/src/utils/thememanager.js +++ b/src/utils/thememanager.js @@ -1,4 +1,4 @@ -const UserError = require('../errors/usererror') +const UserError = require('../errors/usererror'); const ThemeRepos = { 'answers-hitchhiker-theme': 'https://github.com/yext/answers-hitchhiker-theme.git' diff --git a/tests/acceptance/suites/themeupgrader.js b/tests/acceptance/suites/themeupgrader.js new file mode 100644 index 00000000..21faef2a --- /dev/null +++ b/tests/acceptance/suites/themeupgrader.js @@ -0,0 +1,28 @@ +const process = require('process'); +const git = require('simple-git/promise')(); +const { runInPlayground } = require('../setup/playground'); +const ThemeManager = require('../../../src/utils/thememanager'); +const path = require('path'); + +ThemeManager.getRepoForTheme = () => { + return path.resolve(__dirname, '../test-themes/basic-flow'); +}; + +//Silence error logs +const error = console.error; +console.error = jest.fn(); +afterAll(() => console.error = error); + +it('tests upgrade fail', () => runInPlayground(async t => { + await git.cwd(process.cwd()); + await t.jambo('init'); + await t.jambo( + 'import --themeUrl ../test-themes/basic-flow'); + await git.add('.'); + await git.commit('theme'); + await expect(t.jambo('upgrade --branch fail')).rejects.toThrow( + /Remote branch fail not found in upstream origin/); + const diff = await git.diff(); + expect(diff).toBe(''); + })); + From 1f8a7d225fd106aea188944060e060d8c10786e8 Mon Sep 17 00:00:00 2001 From: Yen Truong <36055303+yen-tt@users.noreply.github.com> Date: Tue, 3 Aug 2021 12:32:32 -0400 Subject: [PATCH 10/13] locale fallback acceptance test (#236) Created locale fallback acceptance test - created a new test-theme folder `locale-fallback` with locale_config.json that contains 3 locales: en (default locale), fr (locale fallback for es), and es. - 'locale-fallback' acceptance test: use `locale-fallback` theme and create an index page for en and es, `index.html.hbs` and `index.fr.html.hbs,` using two different templates. When executing jambo page command for en index page, also create a pageConfig for es with the flag `--locales es`. See that es index page fall back on the pageTemplate and config file for fr. J=SLAP-1303 TEST=auto Set `procrastinateCleanup` to true when running the test. See that in the playground folder, the expected config files, pageTemplates, and public html files are build as expected for en, es, and fr. See that acceptance test passed --- tests/acceptance/suites/locale-fallback.js | 25 +++++++++++++++++ .../test-themes/basic-flow/postimport.js | 2 +- .../test-themes/customcommand/postimport.js | 2 +- .../locale-fallback/config/locale_config.json | 27 +++++++++++++++++++ .../test-themes/locale-fallback/postimport.js | 23 ++++++++++++++++ .../locale-fallback/script/core.hbs | 3 +++ .../locale-fallback/static/.gitkeep | 0 .../markup/directanswer.hbs | 1 + .../universal-fallback/page-config.json | 14 ++++++++++ .../universal-fallback/page.html.hbs | 5 ++++ .../script/directanswer.hbs | 3 +++ .../markup/directanswer.hbs | 1 + .../universal-standard/page-config.json | 14 ++++++++++ .../universal-standard/page.html.hbs | 5 ++++ .../script/directanswer.hbs | 3 +++ 15 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 tests/acceptance/suites/locale-fallback.js create mode 100644 tests/acceptance/test-themes/locale-fallback/config/locale_config.json create mode 100755 tests/acceptance/test-themes/locale-fallback/postimport.js create mode 100644 tests/acceptance/test-themes/locale-fallback/script/core.hbs create mode 100644 tests/acceptance/test-themes/locale-fallback/static/.gitkeep create mode 100644 tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/markup/directanswer.hbs create mode 100644 tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/page-config.json create mode 100644 tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/page.html.hbs create mode 100644 tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/script/directanswer.hbs create mode 100644 tests/acceptance/test-themes/locale-fallback/templates/universal-standard/markup/directanswer.hbs create mode 100644 tests/acceptance/test-themes/locale-fallback/templates/universal-standard/page-config.json create mode 100644 tests/acceptance/test-themes/locale-fallback/templates/universal-standard/page.html.hbs create mode 100644 tests/acceptance/test-themes/locale-fallback/templates/universal-standard/script/directanswer.hbs diff --git a/tests/acceptance/suites/locale-fallback.js b/tests/acceptance/suites/locale-fallback.js new file mode 100644 index 00000000..85125dfe --- /dev/null +++ b/tests/acceptance/suites/locale-fallback.js @@ -0,0 +1,25 @@ +const fs = require('fs'); + +const { runInPlayground } = require('../setup/playground'); + +it('check locale fallback functionality for page and config files', + () => runInPlayground(async t => { + await t.jambo('init'); + await t.jambo('import --themeUrl ../test-themes/locale-fallback'); + /** + * Creates index.html.hbs and index.json following universal-standard template. + * Also generates an empty ({}) index.es.json file, which will fallback on index.fr.json + */ + await t.jambo('page --name index --template universal-standard --locales es'); + /** + * Creates index.fr.html.hbs and index.fr.json following universal-fallback template. + * Since es doesn't have an index page template, it will fallback on index.fr.html.hbs + */ + await t.jambo('page --name index.fr --template universal-fallback'); + await t.jambo('build'); + + const localePage = fs.readFileSync('public/es/index.html', 'utf-8'); + const localeFallbackPage = fs.readFileSync( + 'public/fr/index.html', 'utf-8'); + expect(localePage).toEqualHtml(localeFallbackPage); +})); \ No newline at end of file diff --git a/tests/acceptance/test-themes/basic-flow/postimport.js b/tests/acceptance/test-themes/basic-flow/postimport.js index 4f5bd399..c9456d89 100755 --- a/tests/acceptance/test-themes/basic-flow/postimport.js +++ b/tests/acceptance/test-themes/basic-flow/postimport.js @@ -11,7 +11,7 @@ const { assign, stringify } = require('comment-json'); function updateDefaultTheme(themeName) { const jamboConfig = JSON.parse(fs.readFileSync('jambo.json', 'utf-8')); if (jamboConfig.defaultTheme !== themeName) { - const updatedConfig = assign({ defaultTheme: themeName }, jamboConfig); + const updatedConfig = assign(jamboConfig, { defaultTheme: themeName }); fs.writeFileSync('jambo.json', stringify(updatedConfig, null, 2)); } } diff --git a/tests/acceptance/test-themes/customcommand/postimport.js b/tests/acceptance/test-themes/customcommand/postimport.js index 0849f4a3..d2ab534c 100755 --- a/tests/acceptance/test-themes/customcommand/postimport.js +++ b/tests/acceptance/test-themes/customcommand/postimport.js @@ -10,7 +10,7 @@ const { assign, stringify } = require('comment-json'); function updateDefaultTheme(themeName) { const jamboConfig = JSON.parse(fs.readFileSync('jambo.json', 'utf-8')); if (jamboConfig.defaultTheme !== themeName) { - const updatedConfig = assign({ defaultTheme: themeName }, jamboConfig); + const updatedConfig = assign(jamboConfig, { defaultTheme: themeName }); fs.writeFileSync('jambo.json', stringify(updatedConfig, null, 2)); } } diff --git a/tests/acceptance/test-themes/locale-fallback/config/locale_config.json b/tests/acceptance/test-themes/locale-fallback/config/locale_config.json new file mode 100644 index 00000000..234b1cb3 --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/config/locale_config.json @@ -0,0 +1,27 @@ +{ + "default": "en", + "localeConfig": { + "en": { + // "fallback": [""], // allows you to specify locale fallbacks for this locale + // "translationFile": ".po", // the filepath for the translation file + // "urlOverride": "", // provide an override for the url path for this locale if you want it to be different than specified in the urlFormat object + "experienceKey": "testkey" // the unique key of your search configuration for this locale + }, + "fr": { + // "fallback": [""], // allows you to specify locale fallbacks for this locale + // "translationFile": "../../translation/es.po", // the filepath for the translation file + // "urlOverride": "", // provide an override for the url path for this locale if you want it to be different than specified in the urlFormat object + "experienceKey": "testkey-fr" // the unique key of your search configuration for this locale + }, + "es": { + "fallback": ["fr"], // allows you to specify locale fallbacks for this locale + // "translationFile": "../../translation/es.po", // the filepath for the translation file + // "urlOverride": "", // provide an override for the url path for this locale if you want it to be different than specified in the urlFormat object + "experienceKey": "testkey-es" // the unique key of your search configuration for this locale + } + }, + "urlFormat": { + "baseLocale": "{locale}/{pageName}.{pageExt}", + "default": "{pageName}.{pageExt}" + } +} diff --git a/tests/acceptance/test-themes/locale-fallback/postimport.js b/tests/acceptance/test-themes/locale-fallback/postimport.js new file mode 100755 index 00000000..71f13ce1 --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/postimport.js @@ -0,0 +1,23 @@ +#!/usr/bin/env node +const fs = require('fs'); +const fsExtra = require('fs-extra'); +const { assign, stringify } = require('comment-json'); + +/** + * Updates the defaultTheme field in the jambo.json. + * + * @param {string} themeName + */ +function updateDefaultTheme(themeName) { + const jamboConfig = JSON.parse(fs.readFileSync('jambo.json', 'utf-8')); + if (jamboConfig.defaultTheme !== themeName) { + const updatedConfig = assign(jamboConfig, { defaultTheme: themeName }); + fs.writeFileSync('jambo.json', stringify(updatedConfig, null, 2)); + } +} + +updateDefaultTheme('locale-fallback'); +fs.writeFileSync('config/global_config.json', '{}'); +fsExtra.copySync('themes/locale-fallback/config/locale_config.json', + 'config/locale_config.json'); +fsExtra.copySync('themes/locale-fallback/static', 'static'); \ No newline at end of file diff --git a/tests/acceptance/test-themes/locale-fallback/script/core.hbs b/tests/acceptance/test-themes/locale-fallback/script/core.hbs new file mode 100644 index 00000000..a88e95aa --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/script/core.hbs @@ -0,0 +1,3 @@ + diff --git a/tests/acceptance/test-themes/locale-fallback/static/.gitkeep b/tests/acceptance/test-themes/locale-fallback/static/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/markup/directanswer.hbs b/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/markup/directanswer.hbs new file mode 100644 index 00000000..5f000c6a --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/markup/directanswer.hbs @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/page-config.json b/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/page-config.json new file mode 100644 index 00000000..8e8fa0a8 --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/page-config.json @@ -0,0 +1,14 @@ +{ + "pageTitle": "universal page", + "componentSettings": { + /** + "QASubmission": { + "entityId": "", // Set the ID of the entity to use for Q&A submissions, must be of entity type "Organization" + "privacyPolicyUrl": "" // The fully qualified URL to the privacy policy + }, + **/ + "DirectAnswer": { + "anotherDummyConfig": "another dummy config" + } + } +} diff --git a/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/page.html.hbs b/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/page.html.hbs new file mode 100644 index 00000000..4e496479 --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/page.html.hbs @@ -0,0 +1,5 @@ +{{#> script/core }} + {{> templates/universal-standard/script/directanswer }} +{{/script/core }} +{{> templates/universal-standard/markup/directanswer }} +
Hello World!
diff --git a/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/script/directanswer.hbs b/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/script/directanswer.hbs new file mode 100644 index 00000000..4853ed74 --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/templates/universal-fallback/script/directanswer.hbs @@ -0,0 +1,3 @@ +ANSWERS.addComponent("DirectAnswer", Object.assign({}, { + container: "#js-answersDirectAnswer" +}, {{{ json componentSettings.DirectAnswer }}})); \ No newline at end of file diff --git a/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/markup/directanswer.hbs b/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/markup/directanswer.hbs new file mode 100644 index 00000000..5f000c6a --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/markup/directanswer.hbs @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/page-config.json b/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/page-config.json new file mode 100644 index 00000000..a6f21011 --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/page-config.json @@ -0,0 +1,14 @@ +{ + "pageTitle": "universal page", + "componentSettings": { + /** + "QASubmission": { + "entityId": "", // Set the ID of the entity to use for Q&A submissions, must be of entity type "Organization" + "privacyPolicyUrl": "" // The fully qualified URL to the privacy policy + }, + **/ + "DirectAnswer": { + "dummyConfig": "dummy config" + } + } +} diff --git a/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/page.html.hbs b/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/page.html.hbs new file mode 100644 index 00000000..b0f2bd9b --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/page.html.hbs @@ -0,0 +1,5 @@ +{{#> script/core }} + {{> templates/universal-standard/script/directanswer }} +{{/script/core }} +{{> templates/universal-standard/markup/directanswer }} + diff --git a/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/script/directanswer.hbs b/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/script/directanswer.hbs new file mode 100644 index 00000000..4853ed74 --- /dev/null +++ b/tests/acceptance/test-themes/locale-fallback/templates/universal-standard/script/directanswer.hbs @@ -0,0 +1,3 @@ +ANSWERS.addComponent("DirectAnswer", Object.assign({}, { + container: "#js-answersDirectAnswer" +}, {{{ json componentSettings.DirectAnswer }}})); \ No newline at end of file From 2d76f4dfb0828656fdb757b3c934378e964927f8 Mon Sep 17 00:00:00 2001 From: Oliver Shi Date: Wed, 11 Aug 2021 11:16:04 -0400 Subject: [PATCH 11/13] pass partials to templatedatavalidator (#237) I will cut a beta version if this is merged in, so that the theme's CI can work properly. J=SLAP-1489 TEST=manual ran builds in the theme, saw that the partials can be used to validate things --- src/commands/build/pagewriter.js | 3 ++- src/commands/build/templatedatavalidator.js | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/commands/build/pagewriter.js b/src/commands/build/pagewriter.js index d60e3d76..974b28ec 100644 --- a/src/commands/build/pagewriter.js +++ b/src/commands/build/pagewriter.js @@ -72,7 +72,8 @@ module.exports = class PageWriter { if(!this._templateDataValidator.validate({ pageName: page.getName(), - pageData: templateArguments + pageData: templateArguments, + partials: hbs.partials })) { throw new UserError('Invalid page template configuration(s).'); } diff --git a/src/commands/build/templatedatavalidator.js b/src/commands/build/templatedatavalidator.js index e811ceb2..af181cc5 100644 --- a/src/commands/build/templatedatavalidator.js +++ b/src/commands/build/templatedatavalidator.js @@ -1,6 +1,5 @@ const fs = require('file-system'); const UserError = require('../../errors/usererror'); -const { isCustomError } = require('../../utils/errorutils'); const { info } = require('../../utils/logger'); /** @@ -28,21 +27,24 @@ module.exports = class TemplateDataValidator { * @param {Object} page * @param {string} page.pageName name of the current page * @param {Object} page.pageData template arguments for the current page + * @param {Object} partials + * mapping of partial name to partial. Handlebars converts + * partials from strings to Functions when they are used. * @throws {UserError} on failure to execute hook * @returns {boolean} whether or not to throw an exception on bad template arguments */ - validate({ pageName, pageData }) { + validate({ pageName, pageData, partials}) { if (!this._hasHook) { - return true; + return true; } try { - info(`Validating configuration for page "${pageName}".`); - const validatorFunction = require(this._templateDataValidationHook); - return validatorFunction(pageData); + info(`Validating configuration for page "${pageName}".`); + const validatorFunction = require(this._templateDataValidationHook); + return validatorFunction(pageData, partials); } catch (err) { - const msg = - `Error executing validation hook from ${this._templateDataValidationHook}: `; - throw new UserError(msg, err.stack); + const msg = + `Error executing validation hook from ${this._templateDataValidationHook}: `; + throw new UserError(msg, err.stack); } } } From 44b7f797e49a53d5bf6137bc5982cf6eb46b83b3 Mon Sep 17 00:00:00 2001 From: Oliver Shi Date: Wed, 18 Aug 2021 17:44:19 -0400 Subject: [PATCH 12/13] =?UTF-8?q?update=20canonicalizeLocale=20to=20handle?= =?UTF-8?q?=20=E4=B8=AD=E6=96=87(chinese)=20(#239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit J=SLAP-1526 TEST=auto,manual unit tests ran build with zh-hans-ch in locale config, saw that the generated source code used the correct locale in the page tried building a page with a locale with >3 sections, got a jambo warning but build continued without changing the locale --- src/utils/i18nutils.js | 84 ++++++++++++++++++++---- tests/acceptance/suites/themeupgrader.js | 24 +++---- tests/utils/i18nutils.js | 62 ++++++++++++++++- 3 files changed, 145 insertions(+), 25 deletions(-) diff --git a/src/utils/i18nutils.js b/src/utils/i18nutils.js index 54d1a34e..2c681410 100644 --- a/src/utils/i18nutils.js +++ b/src/utils/i18nutils.js @@ -1,25 +1,87 @@ +const UserError = require('../errors/usererror'); + /** * Normalizes a locale code * * @param {string} localeCode * @returns {string} */ -canonicalizeLocale = function(localeCode) { +exports.canonicalizeLocale = function(localeCode) { if (!localeCode) { return; } - const localeCodeSections = localeCode.replace('-', '_') - .split('_'); - - const languageIndex = 0; - const regionIndex = 1; + const { language, modifier, region } = parseLocale(localeCode); + return formatLocale(language, modifier, region); +} - localeCodeSections[languageIndex] = localeCodeSections[languageIndex].toLowerCase(); +/** + * Parses a locale code into its constituent parts. + * Performs case formatting on the result. + * + * @param {string} localeCode + * @returns { language: string, modifier?: string, region?: string } + */ +function parseLocale(localeCode) { + const localeCodeSections = localeCode.replace(/-/g, '_').split('_'); + const language = localeCodeSections[0].toLowerCase(); + const parseModifierAndRegion = () => { + const numSections = localeCodeSections.length; + if (numSections === 1) { + return {}; + } else if (numSections === 2 && language === 'zh') { + const ambiguous = localeCodeSections[1].toLowerCase(); + if (['hans', 'hant'].includes(ambiguous)) { + return { modifier: ambiguous }; + } else { + return { region: ambiguous }; + } + } else if (numSections === 2) { + return { region: localeCodeSections[1] }; + } else if (numSections === 3) { + return { + modifier: localeCodeSections[1], + region: localeCodeSections[2] + }; + } else if (numSections > 3) { + throw new UserError( + `Encountered strangely formatted locale "${localeCode}", ` + + `with ${numSections} sections.`); + } + } + const capitalizeFirstLetterOnly = raw => { + return raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase(); + } + const parsedLocale = { + language, + ...parseModifierAndRegion() + }; - if (localeCodeSections.length > regionIndex) { - localeCodeSections[regionIndex] = localeCodeSections[regionIndex].toUpperCase(); + if (parsedLocale.modifier) { + parsedLocale.modifier = capitalizeFirstLetterOnly(parsedLocale.modifier); + } + if (parsedLocale.region) { + parsedLocale.region = parsedLocale.region.toUpperCase(); } - return localeCodeSections.join('_'); + return parsedLocale; } -exports.canonicalizeLocale = canonicalizeLocale; \ No newline at end of file +exports.parseLocale = parseLocale; + +/** + * Formats a locale code given its constituent parts. + * + * @param {string} language zh in zh-Hans_CH + * @param {string?} modifier Hans in zh-Hans_CH + * @param {string?} region CH in zh-Hans_CH + * @returns + */ +function formatLocale(language, modifier, region) { + let result = language.toLowerCase(); + if (modifier) { + result += '-' + modifier; + } + if (region) { + result += '_' + region; + } + return result; +} \ No newline at end of file diff --git a/tests/acceptance/suites/themeupgrader.js b/tests/acceptance/suites/themeupgrader.js index 21faef2a..7cc22707 100644 --- a/tests/acceptance/suites/themeupgrader.js +++ b/tests/acceptance/suites/themeupgrader.js @@ -5,7 +5,7 @@ const ThemeManager = require('../../../src/utils/thememanager'); const path = require('path'); ThemeManager.getRepoForTheme = () => { - return path.resolve(__dirname, '../test-themes/basic-flow'); + return path.resolve(__dirname, '../test-themes/basic-flow'); }; //Silence error logs @@ -14,15 +14,15 @@ console.error = jest.fn(); afterAll(() => console.error = error); it('tests upgrade fail', () => runInPlayground(async t => { - await git.cwd(process.cwd()); - await t.jambo('init'); - await t.jambo( - 'import --themeUrl ../test-themes/basic-flow'); - await git.add('.'); - await git.commit('theme'); - await expect(t.jambo('upgrade --branch fail')).rejects.toThrow( - /Remote branch fail not found in upstream origin/); - const diff = await git.diff(); - expect(diff).toBe(''); - })); + await git.cwd(process.cwd()); + await t.jambo('init'); + await t.jambo( + 'import --themeUrl ../test-themes/basic-flow'); + await git.add('.'); + await git.commit('theme'); + await expect(t.jambo('upgrade --branch fail')).rejects.toThrow( + /Remote branch fail not found in upstream origin/); + const diff = await git.diff(); + expect(diff).toBe(''); +})); diff --git a/tests/utils/i18nutils.js b/tests/utils/i18nutils.js index daba290e..c466f1aa 100644 --- a/tests/utils/i18nutils.js +++ b/tests/utils/i18nutils.js @@ -1,4 +1,4 @@ -const { canonicalizeLocale } = require('../../src/utils/i18nutils'); +const { canonicalizeLocale, parseLocale } = require('../../src/utils/i18nutils'); describe('canonicalizeLocale correctly normalizes locales', () => { it('converts language to lower case and region to upper case', () => { @@ -12,4 +12,62 @@ describe('canonicalizeLocale correctly normalizes locales', () => { const canonicalizedLocale = canonicalizeLocale(locale); expect(canonicalizedLocale).toEqual('fr_CH'); }); -}); \ No newline at end of file + + describe('works for chinese', () => { + function runTest(testName, inputLocale, expectedLocale) { + it(testName, () => { + expect(canonicalizeLocale(inputLocale)).toEqual(expectedLocale); + }); + } + const testCases = [ + ['using dashes', 'zh-Hans-CH'], + ['using underscores', 'zh_Hans_CH'], + ['underscore then dash', 'zh_Hans-CH'], + ['dash then underscore', 'zh-Hans_CH'], + ['updates casing', 'ZH-hans_Ch'], + ['does not have region code', 'zh-hans', 'zh-Hans'], + ['has region but no modifier', 'zh-cH', 'zh_CH'] + ]; + const expected = 'zh-Hans_CH'; + for (const [testName, inputLocale, specificExpected] of testCases) { + runTest(testName, inputLocale, specificExpected || expected); + } + }); +}); + +describe('parseLocale', () => { + it('performs case formatting', () => { + expect(parseLocale('Zh-hans-Ch')).toEqual({ + language: 'zh', + modifier: 'Hans', + region: 'CH' + }) + }); + + it('chinese with modifier only', () => { + expect(parseLocale('ZH_HANS')).toEqual({ + language: 'zh', + modifier: 'Hans' + }) + }); + + it('chinese with region only', () => { + expect(parseLocale('ZH-cH')).toEqual({ + language: 'zh', + region: 'CH' + }) + }); + + it('2 section non-chinese locale', () => { + expect(parseLocale('FR-freE')).toEqual({ + language: 'fr', + region: 'FREE' + }); + }); + + it('simple language', () => { + expect(parseLocale('FR')).toEqual({ + language: 'fr' + }); + }); +}); From a2464bb214218d9043f130ac17be7ff460584547 Mon Sep 17 00:00:00 2001 From: Oliver Shi Date: Tue, 24 Aug 2021 11:30:04 -0400 Subject: [PATCH 13/13] 1.12.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1f16107b..7797a8fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "jambo", - "version": "1.11.0", + "version": "1.12.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4fadb115..1e0ac1b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jambo", - "version": "1.11.0", + "version": "1.12.0", "description": "A JAMStack implementation using Handlebars", "main": "index.js", "scripts": {