From dd14773443e05881630f98ed3f9aa00e5df2cbb7 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 29 Mar 2020 11:14:08 -0400 Subject: [PATCH 01/55] Use JSDoc for function descriptions --- packages/router/src/util.js | 89 +++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/packages/router/src/util.js b/packages/router/src/util.js index 6bfab4a3e4b2..cfda783ccdde 100644 --- a/packages/router/src/util.js +++ b/packages/router/src/util.js @@ -1,13 +1,15 @@ -// Create a React Context with the given name. +/** Create a React Context with the given name. */ const createNamedContext = (name, defaultValue) => { const Ctx = React.createContext(defaultValue) Ctx.displayName = name return Ctx } -// Get param name and type tranform for a route -// -// '/blog/{year}/{month}/{day:Int}' => [['year'], ['month'], ['day', 'Int']] +/** + * Get param name and type transform for a route + * + * '/blog/{year}/{month}/{day:Int}' => [['year'], ['month'], ['day', 'Int']] + */ const paramsForType = (route) => { // Match the strings between `{` and `}`. const params = [...route.matchAll(/\{([^}]+)\}/g)] @@ -18,7 +20,7 @@ const paramsForType = (route) => { }) } -// Definitions of the core param types. +/** Definitions of the core param types. */ const coreParamTypes = { Int: { constraint: /\d+/, @@ -26,23 +28,25 @@ const coreParamTypes = { }, } -// Determine if the given route is a match for the given pathname. If so, -// extract any named params and return them in an object. -// -// route - The route path as specified in the -// pathname - The pathname from the window.location. -// allParamTypes - The object containing all param type definitions. -// -// Examples: -// -// matchPath('/blog/{year}/{month}/{day}', '/blog/2019/12/07') -// => { match: true, params: { year: '2019', month: '12', day: '07' }} -// -// matchPath('/about', '/') -// => { match: false } -// -// matchPath('/post/{id:Int}', '/post/7') -// => { match: true, params: { id: 7 }} +/** + * Determine if the given route is a match for the given pathname. If so, + * extract any named params and return them in an object. + * + * route - The route path as specified in the + * pathname - The pathname from the window.location. + * allParamTypes - The object containing all param type definitions. + * + * Examples: + * + * matchPath('/blog/{year}/{month}/{day}', '/blog/2019/12/07') + * => { match: true, params: { year: '2019', month: '12', day: '07' }} + * + * matchPath('/about', '/') + * => { match: false } + * + * matchPath('/post/{id:Int}', '/post/7') + * => { match: true, params: { id: 7 }} + */ const matchPath = (route, pathname, paramTypes) => { // Does the `pathname` match the `route`? const matches = [ @@ -73,13 +77,15 @@ const matchPath = (route, pathname, paramTypes) => { return { match: true, params } } -// Parse the given search string into key/value pairs and return them in an -// object. -// -// Examples: -// -// parseSearch('?key1=val1&key2=val2') -// => { key1: 'val1', key2: 'val2' } +/** + * Parse the given search string into key/value pairs and return them in an + * object. + * + * Examples: + * + * parseSearch('?key1=val1&key2=val2') + * => { key1: 'val1', key2: 'val2' } + */ const parseSearch = (search) => { if (search === '') { return {} @@ -94,9 +100,11 @@ const parseSearch = (search) => { return searchProps } -// Validate a path to make sure it follows the router's rules. If any problems -// are found, a descriptive Error will be thrown, as problems with routes are -// critical enough to be considered fatal. +/** + * Validate a path to make sure it follows the router's rules. If any problems + * are found, a descriptive Error will be thrown, as problems with routes are + * critical enough to be considered fatal. + */ const validatePath = (path) => { // Check that path begins with a slash. if (path[0] !== '/') { @@ -116,13 +124,16 @@ const validatePath = (path) => { } } -// Take a given route path and replace any named parameters with those in the -// given args object. Any extra params not used in the path will be appended -// as key=value pairs in the search part. -// -// Examples: -// replaceParams('/tags/{tag}', { tag: 'code', extra: 'foo' }) -// => '/tags/code?extra=foo +/** + * Take a given route path and replace any named parameters with those in the + * given args object. Any extra params not used in the path will be appended + * as key=value pairs in the search part. + * + * Examples: + * + * replaceParams('/tags/{tag}', { tag: 'code', extra: 'foo' }) + * => '/tags/code?extra=foo + */ const replaceParams = (path, args = {}) => { // Split the path apart and replace named parameters with those sent in, // then join it back together. From c126d1adeb1ebb8fd89e95425370bac73f8f3c23 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 29 Mar 2020 11:25:36 -0400 Subject: [PATCH 02/55] Use URLSearchParams --- packages/router/package.json | 3 ++- packages/router/src/router.js | 1 + packages/router/src/util.js | 22 +++++++++----------- yarn.lock | 39 ++++++++++++++++++++--------------- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/packages/router/package.json b/packages/router/package.json index 081d986397b6..0019237439ff 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -7,7 +7,8 @@ "main": "dist/index.js", "license": "MIT", "dependencies": { - "core-js": "3.6.4" + "core-js": "3.6.4", + "url-search-params-polyfill": "^8.0.0" }, "peerDependencies": { "react": "*" diff --git a/packages/router/src/router.js b/packages/router/src/router.js index 6675f8636abf..5b15ad9f2d0c 100644 --- a/packages/router/src/router.js +++ b/packages/router/src/router.js @@ -1,4 +1,5 @@ // The guts of the router implementation. +import 'url-search-params-polyfill'; import { Location, diff --git a/packages/router/src/util.js b/packages/router/src/util.js index cfda783ccdde..a0a9eac9b58f 100644 --- a/packages/router/src/util.js +++ b/packages/router/src/util.js @@ -85,19 +85,17 @@ const matchPath = (route, pathname, paramTypes) => { * * parseSearch('?key1=val1&key2=val2') * => { key1: 'val1', key2: 'val2' } + * + * @fixme + * This utility ignores keys with multiple values such as `?foo=1&foo=2`. */ const parseSearch = (search) => { - if (search === '') { - return {} - } - const searchPart = search.substring(1) - const pairs = searchPart.split('&') - const searchProps = {} - pairs.forEach((pair) => { - const keyval = pair.split('=') - searchProps[keyval[0]] = keyval[1] || '' - }) - return searchProps + const searchParams = new URLSearchParams(search); + + return [...searchParams.keys()].reduce((params, key) => ({ + ...params, + [key]: searchParams.get(key) + }), {}); } /** @@ -107,7 +105,7 @@ const parseSearch = (search) => { */ const validatePath = (path) => { // Check that path begins with a slash. - if (path[0] !== '/') { + if (!path.startsWith('/')) { throw new Error('Route path does not begin with a slash: "' + path + '"') } diff --git a/yarn.lock b/yarn.lock index a31abb6e45d2..50a456c63be8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1173,7 +1173,7 @@ integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== dependencies: exec-sh "^0.3.2" - minimist "^1.2.5" + minimist "^1.2.0" "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" @@ -3187,7 +3187,7 @@ acorn-globals@^4.3.2: resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== dependencies: - acorn "^6.4.1" + acorn "^6.0.1" acorn-walk "^6.0.1" acorn-jsx@^5.1.0: @@ -4562,7 +4562,7 @@ check-node-version@^4.0.2: dependencies: chalk "^3.0.0" map-values "^1.0.1" - minimist "^1.2.5" + minimist "^1.2.0" object-filter "^1.0.2" run-parallel "^1.1.4" semver "^6.3.0" @@ -6284,7 +6284,7 @@ espree@^6.1.2: resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d" integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA== dependencies: - acorn "^7.1.1" + acorn "^7.1.0" acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" @@ -8745,7 +8745,7 @@ jsdom@^15.1.1: integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== dependencies: abab "^2.0.0" - acorn "^7.1.1" + acorn "^7.1.0" acorn-globals "^4.3.2" array-equal "^1.0.0" cssom "^0.4.1" @@ -8826,14 +8826,14 @@ json5@^1.0.1: resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== dependencies: - minimist "^1.2.5" + minimist "^1.2.0" json5@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== dependencies: - minimist "^1.2.5" + minimist "^1.2.0" jsonfile@^4.0.0: version "4.0.0" @@ -9484,7 +9484,7 @@ meow@^3.3.0: decamelize "^1.1.2" loud-rejection "^1.0.0" map-obj "^1.0.1" - minimist "^1.2.5" + minimist "^1.1.3" normalize-package-data "^2.3.4" object-assign "^4.0.1" read-pkg-up "^1.0.1" @@ -9499,7 +9499,7 @@ meow@^4.0.0: camelcase-keys "^4.0.0" decamelize-keys "^1.0.0" loud-rejection "^1.0.0" - minimist "^1.2.5" + minimist "^1.1.3" minimist-options "^3.0.1" normalize-package-data "^2.3.4" read-pkg-up "^3.0.0" @@ -9734,7 +9734,7 @@ mkdirp@^0.5.0, mkdirp@^0.5.1: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: - minimist "0.2.1" + minimist "0.0.8" modify-values@^1.0.0: version "1.0.1" @@ -10325,7 +10325,7 @@ optimist@^0.6.1: resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= dependencies: - minimist "~0.2.1" + minimist "~0.0.1" wordwrap "~0.0.2" optionator@^0.8.1, optionator@^0.8.3: @@ -11232,7 +11232,7 @@ rc@^1.0.1, rc@^1.1.6: dependencies: deep-extend "^0.6.0" ini "~1.3.0" - minimist "^1.2.5" + minimist "^1.2.0" strip-json-comments "~2.0.1" react-hook-form@^4.10.1: @@ -11803,7 +11803,7 @@ rollup@1.29.0: dependencies: "@types/estree" "*" "@types/node" "*" - acorn "^7.1.1" + acorn "^7.1.0" rsvp@^4.8.4: version "4.8.5" @@ -11875,7 +11875,7 @@ sane@^4.0.3: execa "^1.0.0" fb-watchman "^2.0.0" micromatch "^3.1.4" - minimist "^1.2.5" + minimist "^1.1.1" walker "~1.0.5" sax@>=0.6.0: @@ -12614,7 +12614,7 @@ strong-log-transformer@^2.0.0: integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== dependencies: duplexer "^0.1.1" - minimist "^1.2.5" + minimist "^1.2.0" through "^2.3.4" style-loader@^1.1.3: @@ -13351,6 +13351,11 @@ url-parse@^1.4.3, url-parse@^1.4.7: querystringify "^2.1.1" requires-port "^1.0.0" +url-search-params-polyfill@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/url-search-params-polyfill/-/url-search-params-polyfill-8.0.0.tgz#17415ca6815ff0661e07737b84bcc28e708a7875" + integrity sha512-X4BTaEq1UMz9bTbMKQ6r+CippkKBsFWHiP9wycQc7aHH2Ml/Iieuo44+GJDb77pfP71bONYA/nUd4iokYAxVRQ== + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -13549,7 +13554,7 @@ webpack-bundle-analyzer@^3.6.0: resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz#39b3a8f829ca044682bc6f9e011c95deb554aefd" integrity sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g== dependencies: - acorn "^6.4.1" + acorn "^6.0.7" acorn-walk "^6.1.1" bfj "^6.1.1" chalk "^2.4.1" @@ -13662,7 +13667,7 @@ webpack@^4.42.0: "@webassemblyjs/helper-module-context" "1.8.5" "@webassemblyjs/wasm-edit" "1.8.5" "@webassemblyjs/wasm-parser" "1.8.5" - acorn "^6.4.1" + acorn "^6.2.1" ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" From 3cee53bb8bda1623875ca268a79fb3d4d3b88986 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sun, 29 Mar 2020 11:34:48 -0400 Subject: [PATCH 03/55] undo yarn.lock changes --- yarn.lock | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/yarn.lock b/yarn.lock index 50a456c63be8..a31abb6e45d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1173,7 +1173,7 @@ integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== dependencies: exec-sh "^0.3.2" - minimist "^1.2.0" + minimist "^1.2.5" "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" @@ -3187,7 +3187,7 @@ acorn-globals@^4.3.2: resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== dependencies: - acorn "^6.0.1" + acorn "^6.4.1" acorn-walk "^6.0.1" acorn-jsx@^5.1.0: @@ -4562,7 +4562,7 @@ check-node-version@^4.0.2: dependencies: chalk "^3.0.0" map-values "^1.0.1" - minimist "^1.2.0" + minimist "^1.2.5" object-filter "^1.0.2" run-parallel "^1.1.4" semver "^6.3.0" @@ -6284,7 +6284,7 @@ espree@^6.1.2: resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d" integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA== dependencies: - acorn "^7.1.0" + acorn "^7.1.1" acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" @@ -8745,7 +8745,7 @@ jsdom@^15.1.1: integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== dependencies: abab "^2.0.0" - acorn "^7.1.0" + acorn "^7.1.1" acorn-globals "^4.3.2" array-equal "^1.0.0" cssom "^0.4.1" @@ -8826,14 +8826,14 @@ json5@^1.0.1: resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== dependencies: - minimist "^1.2.0" + minimist "^1.2.5" json5@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== dependencies: - minimist "^1.2.0" + minimist "^1.2.5" jsonfile@^4.0.0: version "4.0.0" @@ -9484,7 +9484,7 @@ meow@^3.3.0: decamelize "^1.1.2" loud-rejection "^1.0.0" map-obj "^1.0.1" - minimist "^1.1.3" + minimist "^1.2.5" normalize-package-data "^2.3.4" object-assign "^4.0.1" read-pkg-up "^1.0.1" @@ -9499,7 +9499,7 @@ meow@^4.0.0: camelcase-keys "^4.0.0" decamelize-keys "^1.0.0" loud-rejection "^1.0.0" - minimist "^1.1.3" + minimist "^1.2.5" minimist-options "^3.0.1" normalize-package-data "^2.3.4" read-pkg-up "^3.0.0" @@ -9734,7 +9734,7 @@ mkdirp@^0.5.0, mkdirp@^0.5.1: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: - minimist "0.0.8" + minimist "0.2.1" modify-values@^1.0.0: version "1.0.1" @@ -10325,7 +10325,7 @@ optimist@^0.6.1: resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= dependencies: - minimist "~0.0.1" + minimist "~0.2.1" wordwrap "~0.0.2" optionator@^0.8.1, optionator@^0.8.3: @@ -11232,7 +11232,7 @@ rc@^1.0.1, rc@^1.1.6: dependencies: deep-extend "^0.6.0" ini "~1.3.0" - minimist "^1.2.0" + minimist "^1.2.5" strip-json-comments "~2.0.1" react-hook-form@^4.10.1: @@ -11803,7 +11803,7 @@ rollup@1.29.0: dependencies: "@types/estree" "*" "@types/node" "*" - acorn "^7.1.0" + acorn "^7.1.1" rsvp@^4.8.4: version "4.8.5" @@ -11875,7 +11875,7 @@ sane@^4.0.3: execa "^1.0.0" fb-watchman "^2.0.0" micromatch "^3.1.4" - minimist "^1.1.1" + minimist "^1.2.5" walker "~1.0.5" sax@>=0.6.0: @@ -12614,7 +12614,7 @@ strong-log-transformer@^2.0.0: integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== dependencies: duplexer "^0.1.1" - minimist "^1.2.0" + minimist "^1.2.5" through "^2.3.4" style-loader@^1.1.3: @@ -13351,11 +13351,6 @@ url-parse@^1.4.3, url-parse@^1.4.7: querystringify "^2.1.1" requires-port "^1.0.0" -url-search-params-polyfill@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/url-search-params-polyfill/-/url-search-params-polyfill-8.0.0.tgz#17415ca6815ff0661e07737b84bcc28e708a7875" - integrity sha512-X4BTaEq1UMz9bTbMKQ6r+CippkKBsFWHiP9wycQc7aHH2Ml/Iieuo44+GJDb77pfP71bONYA/nUd4iokYAxVRQ== - url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -13554,7 +13549,7 @@ webpack-bundle-analyzer@^3.6.0: resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz#39b3a8f829ca044682bc6f9e011c95deb554aefd" integrity sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g== dependencies: - acorn "^6.0.7" + acorn "^6.4.1" acorn-walk "^6.1.1" bfj "^6.1.1" chalk "^2.4.1" @@ -13667,7 +13662,7 @@ webpack@^4.42.0: "@webassemblyjs/helper-module-context" "1.8.5" "@webassemblyjs/wasm-edit" "1.8.5" "@webassemblyjs/wasm-parser" "1.8.5" - acorn "^6.2.1" + acorn "^6.4.1" ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" From 6852526fda55c563ee68a9631a75735fc83bbc0d Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Tue, 31 Mar 2020 11:36:58 -0400 Subject: [PATCH 04/55] Remove url-search-params-polyfill --- packages/router/package.json | 3 +-- packages/router/src/router.js | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/router/package.json b/packages/router/package.json index 0019237439ff..081d986397b6 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -7,8 +7,7 @@ "main": "dist/index.js", "license": "MIT", "dependencies": { - "core-js": "3.6.4", - "url-search-params-polyfill": "^8.0.0" + "core-js": "3.6.4" }, "peerDependencies": { "react": "*" diff --git a/packages/router/src/router.js b/packages/router/src/router.js index 5b15ad9f2d0c..6675f8636abf 100644 --- a/packages/router/src/router.js +++ b/packages/router/src/router.js @@ -1,5 +1,4 @@ // The guts of the router implementation. -import 'url-search-params-polyfill'; import { Location, From 2cc90dcec4d4ad35fda7a7be98e6c8e6429f54d4 Mon Sep 17 00:00:00 2001 From: Jai Date: Fri, 3 Apr 2020 22:43:38 +0530 Subject: [PATCH 05/55] Changes for #379, `yarn rw db introspect` --- .../dbCommands/__tests__/dbCommands.test.js | 9 +++++++- .../cli/src/commands/dbCommands/introspect.js | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 packages/cli/src/commands/dbCommands/introspect.js diff --git a/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js b/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js index b331688b26a3..6a1de85f1d65 100644 --- a/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js +++ b/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js @@ -8,12 +8,14 @@ import * as down from '../down' import * as save from '../save' import * as generate from '../generate' import * as seed from '../seed' +import * as introspect from '../introspect' describe('db commands', () => { it('some commands have a verbose flag', () => { expect(up.builder.verbose).toBeDefined() expect(down.builder.verbose).toBeDefined() expect(generate.builder.verbose).toBeDefined() + expect(introspect.builder.verbose).toBeDefined() }) it('runs the command as expected', async () => { @@ -40,7 +42,12 @@ describe('db commands', () => { 'yarn prisma2 generate', ]) + await introspect.handler({}) + expect(runCommandTask.mock.results[5].value).toEqual([ + 'yarn prisma2 introspect', + ]) + await seed.handler({}) - expect(runCommandTask.mock.results[5].value).toEqual(['node seeds.js']) + expect(runCommandTask.mock.results[6].value).toEqual(['node seeds.js']) }) }) diff --git a/packages/cli/src/commands/dbCommands/introspect.js b/packages/cli/src/commands/dbCommands/introspect.js new file mode 100644 index 000000000000..217f4476cfea --- /dev/null +++ b/packages/cli/src/commands/dbCommands/introspect.js @@ -0,0 +1,23 @@ +import { getPaths, runCommandTask } from 'src/lib' + +export const command = 'introspect' +export const desc = 'Introspect your database.' +export const builder = { + verbose: { type: 'boolean', default: true, alias: ['v'] }, +} + +export const handler = async ({ verbose = true }) => { + return await runCommandTask( + [ + { + title: 'Introspecting your database...', + cmd: 'yarn prisma2', + args: ['introspect'], + opts: { cwd: getPaths().api.db }, + }, + ], + { + verbose + } + ) +} From 5f4b1327a32677b8c310f7c56b8f10dfe9d02174 Mon Sep 17 00:00:00 2001 From: Jai Date: Sat, 4 Apr 2020 14:48:27 +0530 Subject: [PATCH 06/55] Included lint in package.json for the cli package. Missed running lint because it wasn't here. --- packages/cli/package.json | 4 +++- packages/cli/src/commands/dbCommands/introspect.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 8443ff6eae41..98c209292e30 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -38,7 +38,9 @@ "clean": "rm -rf dist", "prepublishOnly": "yarn clean && yarn build", "test": "jest", - "test:watch": "yarn test --watch" + "test:watch": "yarn test --watch", + "lint": "yarn eslint './src/'", + "lint:fix": "yarn eslint --fix './src/'" }, "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649" } diff --git a/packages/cli/src/commands/dbCommands/introspect.js b/packages/cli/src/commands/dbCommands/introspect.js index 217f4476cfea..97e6d538ce0b 100644 --- a/packages/cli/src/commands/dbCommands/introspect.js +++ b/packages/cli/src/commands/dbCommands/introspect.js @@ -16,8 +16,8 @@ export const handler = async ({ verbose = true }) => { opts: { cwd: getPaths().api.db }, }, ], - { - verbose + { + verbose, } ) } From b19010c10f5a5de5a13c4325b4274b22fc417f6d Mon Sep 17 00:00:00 2001 From: Nikola Hristov Date: Tue, 7 Apr 2020 08:50:38 +0300 Subject: [PATCH 07/55] Fixes typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dae77fa288d6..906e9d0d2728 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Before interacting with the Redwood community, please read and understand our [C ## Local Package Development Setup -You'll want to run a local redwood app sandbox using your local @redwoodjs packages instead of the current releases from the package registry. To do this we use [`yarn link`](`https://classic.yarnpkg.com/en/docs/cli/link/). +You'll want to run a local redwood app sandbox using your local @redwoodjs packages instead of the current releases from the package registry. To do this we use [`yarn link`](https://classic.yarnpkg.com/en/docs/cli/link/). ### Example Setup: Package `@redwoodjs/cli` Local Dev From 86506fbbe58899855936231b6a09ece6ffac7761 Mon Sep 17 00:00:00 2001 From: David S Price Date: Tue, 7 Apr 2020 00:50:01 -0700 Subject: [PATCH 08/55] remove GH Action Mac build, update title --- .github/workflows/build-eslint-jest.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-eslint-jest.yaml b/.github/workflows/build-eslint-jest.yaml index dcb49fd32d95..1f0a45bd2a23 100644 --- a/.github/workflows/build-eslint-jest.yaml +++ b/.github/workflows/build-eslint-jest.yaml @@ -1,5 +1,4 @@ -name: Build, Lint, and Test against latest Node 12 - +name: Build, Lint, and Test on: push: branches: [master] @@ -11,10 +10,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, windows-latest] node-version: ['12'] fail-fast: false - name: ${{ matrix.os }} latest | Node ${{ matrix.node-version }} latest + name: ${{ matrix.os }} | Node ${{ matrix.node-version }} latest steps: - uses: actions/checkout@v2 - name: Setup node From 064e17238b921d119c3edb22080a614468d97508 Mon Sep 17 00:00:00 2001 From: Jai Date: Tue, 7 Apr 2020 18:19:43 +0530 Subject: [PATCH 09/55] Review comments. --- packages/cli/package.json | 4 +--- packages/cli/src/commands/dbCommands/introspect.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 98c209292e30..8443ff6eae41 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -38,9 +38,7 @@ "clean": "rm -rf dist", "prepublishOnly": "yarn clean && yarn build", "test": "jest", - "test:watch": "yarn test --watch", - "lint": "yarn eslint './src/'", - "lint:fix": "yarn eslint --fix './src/'" + "test:watch": "yarn test --watch" }, "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649" } diff --git a/packages/cli/src/commands/dbCommands/introspect.js b/packages/cli/src/commands/dbCommands/introspect.js index 97e6d538ce0b..fe4268a6f383 100644 --- a/packages/cli/src/commands/dbCommands/introspect.js +++ b/packages/cli/src/commands/dbCommands/introspect.js @@ -1,7 +1,7 @@ import { getPaths, runCommandTask } from 'src/lib' export const command = 'introspect' -export const desc = 'Introspect your database.' +export const desc = 'Introspect your database and generate the models in api/prisma/schema.prisma (overwrites existing models).' export const builder = { verbose: { type: 'boolean', default: true, alias: ['v'] }, } From de46af3009f6ee8135b984e433d35360da38f496 Mon Sep 17 00:00:00 2001 From: Jai Date: Tue, 7 Apr 2020 18:25:09 +0530 Subject: [PATCH 10/55] Linting --- packages/cli/src/commands/dbCommands/introspect.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/dbCommands/introspect.js b/packages/cli/src/commands/dbCommands/introspect.js index fe4268a6f383..dd506d49d8b0 100644 --- a/packages/cli/src/commands/dbCommands/introspect.js +++ b/packages/cli/src/commands/dbCommands/introspect.js @@ -1,7 +1,8 @@ import { getPaths, runCommandTask } from 'src/lib' export const command = 'introspect' -export const desc = 'Introspect your database and generate the models in api/prisma/schema.prisma (overwrites existing models).' +export const desc = + 'Introspect your database and generate the models in api/prisma/schema.prisma (overwrites existing models).' export const builder = { verbose: { type: 'boolean', default: true, alias: ['v'] }, } From 607e481626c9516ff07c18a57a66a6b424d89c52 Mon Sep 17 00:00:00 2001 From: David S Price Date: Wed, 8 Apr 2020 09:54:35 -0700 Subject: [PATCH 11/55] upgrade prisma v2 beta.2 --- packages/api/package.json | 2 +- packages/cli/package.json | 2 +- packages/core/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index a9097f07fc44..ff2cdbb87bda 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -9,7 +9,7 @@ "types": "./dist/main.d.ts", "license": "MIT", "dependencies": { - "@prisma/client": "2.0.0-beta.1", + "@prisma/client": "2.0.0-beta.2", "@redwoodjs/internal": "^0.4.0", "apollo-server-lambda": "2.11.0", "babel-plugin-macros": "^2.8.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 2ebbd9126167..7638a8df10b6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -11,7 +11,7 @@ "dist" ], "dependencies": { - "@prisma/sdk": "^2.0.0-beta.1", + "@prisma/sdk": "^2.0.0-beta.2", "@redwoodjs/internal": "^0.4.0", "camelcase": "^5.3.1", "chalk": "^3.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 2e950344f52e..2fee50a3b612 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -16,7 +16,7 @@ "@babel/preset-react": "^7.9.4", "@babel/preset-typescript": "^7.9.0", "@babel/runtime-corejs3": "^7.9.2", - "@prisma/cli": "2.0.0-beta.1", + "@prisma/cli": "2.0.0-beta.2", "@redwoodjs/cli": "^0.4.0", "@redwoodjs/dev-server": "^0.4.0", "@redwoodjs/eslint-config": "^0.4.0", From 545f776ad429407a1684c6ed195ce9ae36880207 Mon Sep 17 00:00:00 2001 From: David S Price Date: Wed, 8 Apr 2020 11:02:08 -0700 Subject: [PATCH 12/55] update lockfile --- yarn.lock | 88 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/yarn.lock b/yarn.lock index 73af449a46aa..a27c775b76d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2109,23 +2109,23 @@ dependencies: "@types/node" ">= 8" -"@prisma/cli@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.0.0-beta.1.tgz#7e5f13db65244cf723323c3046d63cd744f2f026" - integrity sha512-uMCthsNkLCmQiPs6veMdqT/Y4RWRbn8Y0GAfqvg+qStene6Pqid7DTrdN7hC+VYlY7Ok5Fxcb6oBis1jsOO7pQ== - -"@prisma/client@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.0.0-beta.1.tgz#457d83167cf1657e16473ebf467a9b339d982932" - integrity sha512-4uRHUJs/RGc517wLZPrr9ZRq/NbUoPrX4Pa8mb4wIluJMxk4d1raE0O2/LBM5EcVH0RaoIA+dffTvPGfI3by+g== - -"@prisma/engine-core@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@prisma/engine-core/-/engine-core-2.0.0-beta.1.tgz#f6a0a11c50d0cefcb483ce6dbbe7451056854a96" - integrity sha512-GldPCPFQ2GxdHuGcWtmALC+B3Ch9f+8nzQvEm6Kvf/Jz5Pv9FnlZxKBDpcB8AXc3N0UedHyMUoyIB6RLQFYqiA== - dependencies: - "@prisma/generator-helper" "2.0.0-beta.1" - "@prisma/get-platform" "2.0.0-beta.1" +"@prisma/cli@2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.0.0-beta.2.tgz#5252cbe458fd7de479a6697ab5cffd1659a5762c" + integrity sha512-lD0cv/YC4g4SKByxH3hvzX1oS9cjRdCDU1Sh2inATaKcTHWPlxxgXQW4yYU2FF/HSWR9KK6YMAdO3ShkOl1PEQ== + +"@prisma/client@2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.0.0-beta.2.tgz#dd7b357f65e8100cd6cc1b610d848b72217640bb" + integrity sha512-VhNYeeGTrFfLjB/JL7XPoWpxSTCb+YV+F3ggR1X0CGa4b/u6Kxy65aElLozs+yFdMQPVNkyg2PwUO8xQ7G9fpg== + +"@prisma/engine-core@2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@prisma/engine-core/-/engine-core-2.0.0-beta.2.tgz#ce815e2d14fab7c06f3329f548e221a96b6b4ca6" + integrity sha512-dlWxlsBL4tKNAOUS9Hc9311DvwEWhZp+aBb17shusZGEUnHDz4oeVdIBXkYBCNBL6XVW7/sy+NUfLSJX1jSYtw== + dependencies: + "@prisma/generator-helper" "2.0.0-beta.2" + "@prisma/get-platform" "2.0.0-beta.2" bent "^7.0.6" camelcase "^5.3.1" chalk "^3.0.0" @@ -2133,16 +2133,17 @@ debug "^4.1.1" indent-string "^4.0.0" -"@prisma/fetch-engine@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-2.0.0-beta.1.tgz#242383195f3a705f4e96939f4393e1d38b206e58" - integrity sha512-278Fn8/Dza3qJP6WUEP2PHSsa0tYvrUxMPeuNoAYSqXDiBtnWXkR//4f6OftIQBW2yK2yf7p1+tALJ85+RsX7Q== +"@prisma/fetch-engine@2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-2.0.0-beta.2.tgz#04e985e0ef8fd39a78e120a4ae6726960537a358" + integrity sha512-WJ2AlQWoPQCS/oM9TG4RQAhXhuKi0C82uosL7PNpc7+vjzuWn3OK8zRgm2/m+d2XYMOKOlC6jUR2TkLD402i1g== dependencies: - "@prisma/get-platform" "2.0.0-beta.1" + "@prisma/get-platform" "2.0.0-beta.2" chalk "^3.0.0" debug "^4.1.1" execa "~3.4.0" find-cache-dir "^3.2.0" + hasha "^5.2.0" htmlparser2 "^4.0.0" http-proxy-agent "^2.1.0" https-proxy-agent "^3.0.1" @@ -2155,10 +2156,10 @@ rimraf "^3.0.0" tempy "^0.5.0" -"@prisma/generator-helper@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@prisma/generator-helper/-/generator-helper-2.0.0-beta.1.tgz#64c19f291eeabc8e4a779471d96c34a88f3a558c" - integrity sha512-5fgv52o5HBSIe3yW1xHJxiI0RNkwnJMLeQGZwr792qNRT747rXXt0Q7CxC2ksnYzZRNUPhEjjHL9cvK9dRE7VQ== +"@prisma/generator-helper@2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@prisma/generator-helper/-/generator-helper-2.0.0-beta.2.tgz#11d52038e24d2ac7d2124d10d5a8c98c589054a8" + integrity sha512-zSY5Ee8NddOBTqNqkARWWsp9291MjnkBz1cw3UcuvOHbB1+iYXOuLFmbyc6UBcNALoffJVdqsrJGjh1nWcNS7w== dependencies: "@types/cross-spawn" "^6.0.1" chalk "^3.0.0" @@ -2166,23 +2167,23 @@ debug "4.1.1" isbinaryfile "^4.0.2" -"@prisma/get-platform@2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-2.0.0-beta.1.tgz#7e27d1b21fcbc5db8db1232c162b71b2ea708090" - integrity sha512-ARDkRldzILGYOqRCvtPeujGVm1D46xCIIbHU9LFCw8RhALSw8g9tBssX+nBo+JTt6O+29DKk9J7xt8OQknWcwQ== +"@prisma/get-platform@2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-2.0.0-beta.2.tgz#4a6e684b3afb53d8eda1a1f03aa0c4159661fd5e" + integrity sha512-QO7u+1rWaWGmK4p/77mLzP1jcxqHrQ/+lhN6nPJefMCq3FxBdlZBuKPLFwPkjAMwxYHIaJut+r4+954S17gDQQ== dependencies: debug "^4.1.1" -"@prisma/sdk@^2.0.0-beta.1": - version "2.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@prisma/sdk/-/sdk-2.0.0-beta.1.tgz#22d315ab52f0ebdb36eeced7e4e3225f6b5c7bc6" - integrity sha512-gDVJUDzJ6LNBMxmlNdZm/4f/gHUwm+IC/Dg6jsjQwZ85rbC5GSYDfBt2IVCzqPiOt0maScCxnzPutL1hb8viuQ== +"@prisma/sdk@^2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@prisma/sdk/-/sdk-2.0.0-beta.2.tgz#7d20c68c6d60ad090c65af9208f85a4eecf86e15" + integrity sha512-Q48ABuSh/CViuKLZ6Fa69WYALycCshus2PZiywifkqBfz4wVC0Clvdu0fyrmUydnZFxLJFunMHp1jwKHsPIvzw== dependencies: "@apexearth/copy" "^1.4.4" - "@prisma/engine-core" "2.0.0-beta.1" - "@prisma/fetch-engine" "2.0.0-beta.1" - "@prisma/generator-helper" "2.0.0-beta.1" - "@prisma/get-platform" "2.0.0-beta.1" + "@prisma/engine-core" "2.0.0-beta.2" + "@prisma/fetch-engine" "2.0.0-beta.2" + "@prisma/generator-helper" "2.0.0-beta.2" + "@prisma/get-platform" "2.0.0-beta.2" archiver "3.0.0" arg "4.1.2" chalk "3.0.0" @@ -2202,6 +2203,7 @@ strip-ansi "6.0.0" strip-indent "3.0.0" tar "^5.0.5" + temp-dir "^2.0.0" temp-write "^4.0.0" tempy "^0.3.0" terminal-link "^2.1.1" @@ -7387,6 +7389,14 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.0.tgz#33094d1f69c40a4a6ac7be53d5fe3ff95a269e0c" + integrity sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -13033,7 +13043,7 @@ type-fest@^0.6.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== -type-fest@^0.8.1: +type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== From 432657437e1219e7f420087d5adc9858c061b89e Mon Sep 17 00:00:00 2001 From: David S Price Date: Wed, 8 Apr 2020 22:43:01 -0700 Subject: [PATCH 13/55] add cross-env to yarn rw dev for Windows support --- packages/cli/src/commands/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index ac3aa707edb6..debf28f89de3 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -28,7 +28,7 @@ export const handler = async ({ const execCommandsForApps = { api: { cwd: `${BASE_DIR}/api`, - cmd: 'NODE_ENV=production babel src --out-dir dist', + cmd: 'yarn cross-env NODE_ENV=production babel src --out-dir dist', }, web: { cwd: `${BASE_DIR}/web`, From 3b584c284a062b1e4fa2115aacbf2ac4852aab1b Mon Sep 17 00:00:00 2001 From: Robert Broersma Date: Fri, 10 Apr 2020 12:23:17 +0200 Subject: [PATCH 14/55] Export useApolloClient for one-off queries --- packages/web/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/index.js b/packages/web/src/index.js index cfe8eab0f84d..06e99cd513b9 100644 --- a/packages/web/src/index.js +++ b/packages/web/src/index.js @@ -2,6 +2,6 @@ export { __REDWOOD__ } from './config' export { default as RedwoodProvider, useAuth } from './RedwoodProvider' export { default as gql } from 'graphql-tag' export * from './graphql' -export { useQuery, useMutation } from '@apollo/react-hooks' +export { useQuery, useMutation, useApolloClient } from '@apollo/react-hooks' export * from './form/form' export { default as FatalErrorBoundary } from './FatalErrorBoundary' From 8d510dbfb3c4c88c0d6448a9d58771a8eef547d7 Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Fri, 10 Apr 2020 19:16:15 +0200 Subject: [PATCH 15/55] Add tests for CLI generators (#401) * Remove global mocks, localize to test. * Remove more global mocks. * Make dbCommand tests work again. * Write snapshot tests for generators. * Add generate tests and snaps. * Play around with snapshot testing. * Update snapshots. * Complete reorg of generator files and templates, adds text and fixture directories Cell test working * Component, layout, page and service tests * Adds tests for generator helpers.js * More tests * Comment out some variables that aren't being used for now * Adds multi-word cell tests * Variable name change * Adds multi-word component tests * Component test fix * Adds multi-word layout tests * Adds multi-word page tests * Creates multi-word and CRUD sdl tests * Adds multi word tests for service * Adds scaffold tests * Count actual number of files returned * Rename test directories to __tests__ * Adds tests for src/lib/index * Ignore any files named "test" inside a fixtures directory * Adds generateTemplate tests * Ignore fixture files when linting * fix babel build ignore error using rimraf (rm -rf) Babel flag --no-copy-ignored has a bug and does not respect our ignored config. For a short term fix, I'm using rimraf package (cross-platform support) to clean up __tests__ folders that shouldn't be in dist/ * Converts SDL generator to use same sub-generator technique as scaffold generator * Updates README with new generator file structure * Removes .fixture extensions * Remove unused argument Co-authored-by: Rob Cameron Co-authored-by: David S Price --- package.json | 5 +- packages/cli/__mocks__/@redwoodjs/internal.js | 15 -- packages/cli/__mocks__/fs.js | 5 - packages/cli/jest.config.js | 1 + packages/cli/package.json | 5 +- .../dbCommands/__tests__/dbCommands.test.js | 20 ++- packages/cli/src/commands/generate.js | 2 +- packages/cli/src/commands/generate/README.md | 24 +-- .../generate/__tests__/helpers.test.js | 119 +++++++++++++ .../generate/cell/__tests__/cell.test.js | 45 +++++ .../cell/__tests__/fixtures/multiWordCell.js | 17 ++ .../__tests__/fixtures/multiWordCell.test.js | 34 ++++ .../cell/__tests__/fixtures/singleWordCell.js | 17 ++ .../__tests__/fixtures/singleWordCell.test.js | 34 ++++ .../generate/{commands => cell}/cell.js | 6 +- .../cell => cell/templates}/cell.js.template | 0 .../cell => cell/templates}/test.js.template | 0 .../component/__tests__/component.test.js | 43 +++++ .../__tests__/fixtures/multiWordComponent.js | 10 ++ .../fixtures/multiWordComponent.test.js | 14 ++ .../__tests__/fixtures/singleWordComponent.js | 10 ++ .../fixtures/singleWordComponent.test.js | 14 ++ .../{commands => component}/component.js | 6 +- .../templates}/component.js.template | 0 .../templates}/test.js.template | 0 packages/cli/src/commands/generate/helpers.js | 26 ++- .../__tests__/fixtures/multiWordLayout.js | 5 + .../fixtures/multiWordLayout.test.js | 14 ++ .../__tests__/fixtures/singleWordLayout.js | 5 + .../fixtures/singleWordLayout.test.js | 14 ++ .../generate/layout/__tests__/layout.test.js | 45 +++++ .../generate/{commands => layout}/layout.js | 6 +- .../templates}/layout.js.template | 0 .../templates}/test.js.template | 0 .../page/__tests__/fixtures/multiWordPage.js | 10 ++ .../__tests__/fixtures/multiWordPage.test.js | 14 ++ .../page/__tests__/fixtures/singleWordPage.js | 10 ++ .../__tests__/fixtures/singleWordPage.test.js | 14 ++ .../generate/page/__tests__/page.test.js | 69 ++++++++ .../generate/{commands => page}/page.js | 14 +- .../page => page/templates}/page.js.template | 0 .../page => page/templates}/test.js.template | 0 .../__tests__/fixtures/assets/scaffold.css} | 0 .../__tests__/fixtures/components/editCell.js | 49 +++++ .../__tests__/fixtures/components/form.js | 132 ++++++++++++++ .../__tests__/fixtures/components/index.js | 113 ++++++++++++ .../fixtures/components/indexCell.js | 41 +++++ .../__tests__/fixtures/components/new.js | 36 ++++ .../__tests__/fixtures/components/show.js | 84 +++++++++ .../__tests__/fixtures/components/showCell.js | 23 +++ .../__tests__/fixtures/layouts/layout.js | 30 ++++ .../__tests__/fixtures/pages/editPage.js | 12 ++ .../__tests__/fixtures/pages/indexPage.js | 12 ++ .../__tests__/fixtures/pages/newPage.js | 12 ++ .../__tests__/fixtures/pages/showPage.js | 12 ++ .../scaffold/__tests__/fixtures/schema.prisma | 36 ++++ .../scaffold/__tests__/scaffold.test.js | 163 +++++++++++++++++ .../{commands => scaffold}/scaffold.js | 45 +++-- .../templates/assets/scaffold.css.template | 167 ++++++++++++++++++ .../components/EditNameCell.js.template | 0 .../templates}/components/Name.js.template | 3 +- .../components/NameCell.js.template | 0 .../components/NameForm.js.template | 0 .../templates}/components/Names.js.template | 0 .../components/NamesCell.js.template | 0 .../templates}/components/NewName.js.template | 0 .../layouts/NamesLayout.js.template | 0 .../templates}/pages/EditNamePage.js.template | 0 .../templates}/pages/NamePage.js.template | 0 .../templates}/pages/NamesPage.js.template | 0 .../templates}/pages/NewNamePage.js.template | 0 .../sdl/__tests__/fixtures/multiWordSdl.js | 17 ++ .../__tests__/fixtures/multiWordSdlCrud.js | 24 +++ .../sdl/__tests__/fixtures/schema.prisma | 37 ++++ .../sdl/__tests__/fixtures/singleWordSdl.js | 19 ++ .../__tests__/fixtures/singleWordSdlCrud.js | 31 ++++ .../generate/sdl/__tests__/sdl.test.js | 80 +++++++++ .../generate/{commands => sdl}/sdl.js | 33 ++-- .../sdl => sdl/templates}/sdl.js.template | 0 .../__tests__/fixtures/multiWordService.js | 5 + .../fixtures/multiWordService.test.js | 7 + .../fixtures/multiWordServiceCrud.js | 30 ++++ .../fixtures/multiWordServiceCrud.test.js | 7 + .../__tests__/fixtures/singleWordService.js | 5 + .../fixtures/singleWordService.test.js | 7 + .../fixtures/singleWordServiceCrud.js | 30 ++++ .../fixtures/singleWordServiceCrud.test.js | 7 + .../service/__tests__/service.test.js | 74 ++++++++ .../generate/{commands => service}/service.js | 6 +- .../templates}/service.js.template | 0 .../templates}/test.js.template | 0 packages/cli/src/lib/__mocks__/index.js | 5 - .../cli/src/lib/__tests__/fixtures/code.js | 2 + .../fixtures/postSchema.json.fixture | 99 +++++++++++ .../lib/__tests__/fixtures/prettier.config.js | 8 + .../src/lib/__tests__/fixtures/schema.prisma | 37 ++++ .../cli/src/lib/__tests__/fixtures/text.txt | 2 + packages/cli/src/lib/__tests__/index.test.js | 103 +++++++++++ packages/cli/src/lib/index.js | 25 ++- packages/cli/src/lib/test.js | 84 +++++++++ yarn.lock | 2 +- 101 files changed, 2329 insertions(+), 119 deletions(-) delete mode 100644 packages/cli/__mocks__/@redwoodjs/internal.js delete mode 100644 packages/cli/__mocks__/fs.js create mode 100644 packages/cli/src/commands/generate/__tests__/helpers.test.js create mode 100644 packages/cli/src/commands/generate/cell/__tests__/cell.test.js create mode 100644 packages/cli/src/commands/generate/cell/__tests__/fixtures/multiWordCell.js create mode 100644 packages/cli/src/commands/generate/cell/__tests__/fixtures/multiWordCell.test.js create mode 100644 packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.js create mode 100644 packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.test.js rename packages/cli/src/commands/generate/{commands => cell}/cell.js (88%) rename packages/cli/src/commands/generate/{templates/cell => cell/templates}/cell.js.template (100%) rename packages/cli/src/commands/generate/{templates/cell => cell/templates}/test.js.template (100%) create mode 100644 packages/cli/src/commands/generate/component/__tests__/component.test.js create mode 100644 packages/cli/src/commands/generate/component/__tests__/fixtures/multiWordComponent.js create mode 100644 packages/cli/src/commands/generate/component/__tests__/fixtures/multiWordComponent.test.js create mode 100644 packages/cli/src/commands/generate/component/__tests__/fixtures/singleWordComponent.js create mode 100644 packages/cli/src/commands/generate/component/__tests__/fixtures/singleWordComponent.test.js rename packages/cli/src/commands/generate/{commands => component}/component.js (85%) rename packages/cli/src/commands/generate/{templates/component => component/templates}/component.js.template (100%) rename packages/cli/src/commands/generate/{templates/component => component/templates}/test.js.template (100%) create mode 100644 packages/cli/src/commands/generate/layout/__tests__/fixtures/multiWordLayout.js create mode 100644 packages/cli/src/commands/generate/layout/__tests__/fixtures/multiWordLayout.test.js create mode 100644 packages/cli/src/commands/generate/layout/__tests__/fixtures/singleWordLayout.js create mode 100644 packages/cli/src/commands/generate/layout/__tests__/fixtures/singleWordLayout.test.js create mode 100644 packages/cli/src/commands/generate/layout/__tests__/layout.test.js rename packages/cli/src/commands/generate/{commands => layout}/layout.js (87%) rename packages/cli/src/commands/generate/{templates/layout => layout/templates}/layout.js.template (100%) rename packages/cli/src/commands/generate/{templates/layout => layout/templates}/test.js.template (100%) create mode 100644 packages/cli/src/commands/generate/page/__tests__/fixtures/multiWordPage.js create mode 100644 packages/cli/src/commands/generate/page/__tests__/fixtures/multiWordPage.test.js create mode 100644 packages/cli/src/commands/generate/page/__tests__/fixtures/singleWordPage.js create mode 100644 packages/cli/src/commands/generate/page/__tests__/fixtures/singleWordPage.test.js create mode 100644 packages/cli/src/commands/generate/page/__tests__/page.test.js rename packages/cli/src/commands/generate/{commands => page}/page.js (83%) rename packages/cli/src/commands/generate/{templates/page => page/templates}/page.js.template (100%) rename packages/cli/src/commands/generate/{templates/page => page/templates}/test.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold/assets/scaffold.css.template => scaffold/__tests__/fixtures/assets/scaffold.css} (100%) create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/editCell.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/form.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/index.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/indexCell.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/new.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/show.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/showCell.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/layouts/layout.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/editPage.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/indexPage.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/newPage.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/showPage.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/schema.prisma create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js rename packages/cli/src/commands/generate/{commands => scaffold}/scaffold.js (82%) create mode 100644 packages/cli/src/commands/generate/scaffold/templates/assets/scaffold.css.template rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/components/EditNameCell.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/components/Name.js.template (98%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/components/NameCell.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/components/NameForm.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/components/Names.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/components/NamesCell.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/components/NewName.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/layouts/NamesLayout.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/pages/EditNamePage.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/pages/NamePage.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/pages/NamesPage.js.template (100%) rename packages/cli/src/commands/generate/{templates/scaffold => scaffold/templates}/pages/NewNamePage.js.template (100%) create mode 100644 packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js create mode 100644 packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js create mode 100644 packages/cli/src/commands/generate/sdl/__tests__/fixtures/schema.prisma create mode 100644 packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js create mode 100644 packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js create mode 100644 packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js rename packages/cli/src/commands/generate/{commands => sdl}/sdl.js (84%) rename packages/cli/src/commands/generate/{templates/sdl => sdl/templates}/sdl.js.template (100%) create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.test.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.test.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.test.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.test.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/service.test.js rename packages/cli/src/commands/generate/{commands => service}/service.js (89%) rename packages/cli/src/commands/generate/{templates/service => service/templates}/service.js.template (100%) rename packages/cli/src/commands/generate/{templates/service => service/templates}/test.js.template (100%) delete mode 100644 packages/cli/src/lib/__mocks__/index.js create mode 100644 packages/cli/src/lib/__tests__/fixtures/code.js create mode 100644 packages/cli/src/lib/__tests__/fixtures/postSchema.json.fixture create mode 100644 packages/cli/src/lib/__tests__/fixtures/prettier.config.js create mode 100644 packages/cli/src/lib/__tests__/fixtures/schema.prisma create mode 100644 packages/cli/src/lib/__tests__/fixtures/text.txt create mode 100644 packages/cli/src/lib/__tests__/index.test.js create mode 100644 packages/cli/src/lib/test.js diff --git a/package.json b/package.json index f6354a28264e..d742f8d9695e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,10 @@ "typescript": "^3.8.3" }, "eslintConfig": { - "extends": "@redwoodjs/eslint-config" + "extends": "@redwoodjs/eslint-config", + "ignorePatterns": [ + "fixtures" + ] }, "eslintIgnore": [ "dist", diff --git a/packages/cli/__mocks__/@redwoodjs/internal.js b/packages/cli/__mocks__/@redwoodjs/internal.js deleted file mode 100644 index 7ffbf87f7fb0..000000000000 --- a/packages/cli/__mocks__/@redwoodjs/internal.js +++ /dev/null @@ -1,15 +0,0 @@ -import path from 'path' - -const BASE_PATH = '.' - -export const getPaths = () => ({ - base: BASE_PATH, - api: { - src: path.resolve(BASE_PATH, './api/src'), - services: path.resolve(BASE_PATH, './api/src/services'), - graphql: path.resolve(BASE_PATH, './api/src/graphql'), - }, - web: { - routes: './web/src/Routes.js', - }, -}) diff --git a/packages/cli/__mocks__/fs.js b/packages/cli/__mocks__/fs.js deleted file mode 100644 index 0f37009815be..000000000000 --- a/packages/cli/__mocks__/fs.js +++ /dev/null @@ -1,5 +0,0 @@ -export const readFileSync = () => { - console.log('hello') - - return '' -} diff --git a/packages/cli/jest.config.js b/packages/cli/jest.config.js index 420651a55619..02dfcda2ef4c 100644 --- a/packages/cli/jest.config.js +++ b/packages/cli/jest.config.js @@ -1,3 +1,4 @@ module.exports = { testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/*.test.[jt]s?(x)'], + testPathIgnorePatterns: ['fixtures'], } diff --git a/packages/cli/package.json b/packages/cli/package.json index 2ebbd9126167..05965c4d827f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -27,13 +27,16 @@ "pascalcase": "^1.0.0", "pluralize": "^8.0.0", "prettier": "^2.0.2", + "rimraf": "^3.0.2", "yargs": "^15.3.1" }, "devDependencies": { "@types/node-fetch": "^2.5.5" }, "scripts": { - "build": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start --copy-files --no-copy-ignored", + "build": "yarn build:all && yarn build:clean", + "build:all": "yarn cross-env NODE_ENV=production yarn babel src -d dist --delete-dir-on-start --copy-files --no-copy-ignored", + "build:clean": "yarn rimraf 'dist/**/__tests__'", "build:watch": "nodemon --ignore dist --exec 'yarn build'", "test": "jest", "test:watch": "yarn test --watch" diff --git a/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js b/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js index 99217d42d325..6567df000737 100644 --- a/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js +++ b/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js @@ -1,6 +1,3 @@ -jest.mock('@redwoodjs/internal') -jest.mock('src/lib') - import { runCommandTask } from 'src/lib' import * as up from '../up' @@ -9,7 +6,24 @@ import * as save from '../save' import * as generate from '../generate' import * as seed from '../seed' +jest.mock('src/lib', () => { + return { + ...require.requireActual('src/lib'), + runCommandTask: jest.fn((commands) => { + return commands.map(({ cmd, args }) => `${cmd} ${args.join(' ')}`) + }), + getPaths: () => ({ + api: {}, + web: {}, + }), + } +}) + describe('db commands', () => { + afterAll(() => { + jest.clearAllMocks() + }) + it('some commands have a verbose flag', () => { expect(up.builder.verbose).toBeDefined() expect(down.builder.verbose).toBeDefined() diff --git a/packages/cli/src/commands/generate.js b/packages/cli/src/commands/generate.js index 737c57eadb7e..5fd61fe10afa 100644 --- a/packages/cli/src/commands/generate.js +++ b/packages/cli/src/commands/generate.js @@ -3,4 +3,4 @@ export const aliases = ['g'] export const desc = 'Save time by generating boilerplate code.' export const builder = (yargs) => - yargs.commandDir('./generate/commands').demandCommand().argv + yargs.commandDir('./generate', { recurse: true }).demandCommand().argv diff --git a/packages/cli/src/commands/generate/README.md b/packages/cli/src/commands/generate/README.md index ded5b44b99c7..f4963c2188ab 100644 --- a/packages/cli/src/commands/generate/README.md +++ b/packages/cli/src/commands/generate/README.md @@ -2,11 +2,11 @@ ## Creating a Generator -Generators go in `src/commands/generate/commands` and should be named for the generate command that invokes them (not required). For example, for page generator invoked with: +Generators go in `src/commands/generate` and should be named for the generate command that invokes them (not required). For example, for page generator invoked with: redwood generate page foo -you would create `src/commands/generate/commands/page.js`. The file name does not have to match the generator command (the name is pulled from the object returned when importing the generator) but it is clearest to have the command and the name match. +you would create `src/commands/generate/page/page.js`. The file name does not have to match the generator command (the name is pulled from the object returned when importing the generator) but it is clearest to have the command and the name match. The generator must export the following: @@ -19,21 +19,11 @@ A typicall generator writes files. ## Templates -Templates for the files created by generators go in `src/commands/generate/templates` and should be named after the command that invokes your generator. The files inside should end in `.template` to avoid being compiled by Babel. +Templates for the files created by generators go in `src/commands/generate/[name]/templates` and should be named after the command that invokes your generator. The files inside should end in `.template` to avoid being compiled by Babel. src/commands/generate/ - ├── generators - │ ├── component.js - │ └── page.js - └── templates - ├── component - │ ├── component.js.template - │ ├── readme.mdx.template + └── page + ├── templates + │ ├── page.js.template │ └── test.js.template - └── page - └── page.js.template - -## TODO - -- Check for existence of route before writing, console output skipping if already exists -- Remove `import` statements from service files once photon is automatically included + └── page.js diff --git a/packages/cli/src/commands/generate/__tests__/helpers.test.js b/packages/cli/src/commands/generate/__tests__/helpers.test.js new file mode 100644 index 000000000000..13cd903c6b20 --- /dev/null +++ b/packages/cli/src/commands/generate/__tests__/helpers.test.js @@ -0,0 +1,119 @@ +global.__dirname = __dirname +import {} from 'src/lib/test' + +import * as helpers from '../helpers' + +const PAGE_TEMPLATE_OUTPUT = `const FooBarPage = () => { + return ( +
+

FooBarPage

+

Find me in ./web/src/pages/FooBarPage/FooBarPage.js

+
+ ) +} + +export default FooBarPage +` + +test('templateForComponentFile creates a proper output path for files', () => { + const names = ['FooBar', 'fooBar', 'foo-bar', 'foo_bar', 'FOO_BAR'] + + names.forEach((name) => { + const output = helpers.templateForComponentFile({ + name: name, + suffix: 'Page', + webPathSection: 'pages', + generator: 'page', + templatePath: 'page.js.template', + }) + + expect(output[0]).toEqual( + '/path/to/project/web/src/pages/FooBarPage/FooBarPage.js' + ) + }) +}) + +test('templateForComponentFile can create a path in /web', () => { + const output = helpers.templateForComponentFile({ + name: 'Home', + suffix: 'Page', + webPathSection: 'pages', + generator: 'page', + templatePath: 'page.js.template', + }) + + expect(output[0]).toEqual( + '/path/to/project/web/src/pages/HomePage/HomePage.js' + ) +}) + +test('templateForComponentFile can create a path in /api', () => { + const output = helpers.templateForComponentFile({ + name: 'Home', + suffix: 'Page', + apiPathSection: 'services', + generator: 'page', + templatePath: 'page.js.template', + }) + + expect(output[0]).toEqual( + '/path/to/project/api/src/services/HomePage/HomePage.js' + ) +}) + +test('templateForComponentFile can override generated component name', () => { + const output = helpers.templateForComponentFile({ + name: 'Home', + componentName: 'Hobbiton', + webPathSection: 'pages', + generator: 'page', + templatePath: 'page.js.template', + }) + + expect(output[0]).toEqual( + '/path/to/project/web/src/pages/Hobbiton/Hobbiton.js' + ) +}) + +test('templateForComponentFile can override file extension', () => { + const output = helpers.templateForComponentFile({ + name: 'Home', + suffix: 'Page', + extension: '.txt', + webPathSection: 'pages', + generator: 'page', + templatePath: 'page.js.template', + }) + + expect(output[0]).toEqual( + '/path/to/project/web/src/pages/HomePage/HomePage.txt' + ) +}) + +test('templateForComponentFile creates a template', () => { + const output = helpers.templateForComponentFile({ + name: 'FooBar', + suffix: 'Page', + webPathSection: 'pages', + generator: 'page', + templatePath: 'page.js.template', + }) + + expect(output[1]).toEqual(PAGE_TEMPLATE_OUTPUT) +}) + +test('pathName uses passed path if present', () => { + const paths = ['FooBar', 'fooBar', 'foo-bar', 'foo_bar', 'foobar', '/foobar'] + + paths.forEach((path) => { + expect(helpers.pathName(path, 'FooBar')).toEqual(path) + }) +}) + +test('pathName creates path based on name if path is null', () => { + const names = ['FooBar', 'fooBar', 'foo-bar', 'foo_bar', 'FOO_BAR'] + + names.forEach((name) => { + expect(helpers.pathName(null, name)).toEqual('/foo-bar') + }) +}) diff --git a/packages/cli/src/commands/generate/cell/__tests__/cell.test.js b/packages/cli/src/commands/generate/cell/__tests__/cell.test.js new file mode 100644 index 000000000000..99e460dd768a --- /dev/null +++ b/packages/cli/src/commands/generate/cell/__tests__/cell.test.js @@ -0,0 +1,45 @@ +global.__dirname = __dirname +import { loadGeneratorFixture } from 'src/lib/test' + +import * as cell from '../cell' + +let singleWordFiles, multiWordFiles + +beforeAll(() => { + singleWordFiles = cell.files({ name: 'User' }) + multiWordFiles = cell.files({ name: 'UserProfile' }) +}) + +test('returns exactly 2 files', () => { + expect(Object.keys(singleWordFiles).length).toEqual(2) +}) + +test('creates a cell component with a single word name', () => { + expect( + singleWordFiles['/path/to/project/web/src/components/UserCell/UserCell.js'] + ).toEqual(loadGeneratorFixture('cell', 'singleWordCell.js')) +}) + +test('creates a cell test with a single word name', () => { + expect( + singleWordFiles[ + '/path/to/project/web/src/components/UserCell/UserCell.test.js' + ] + ).toEqual(loadGeneratorFixture('cell', 'singleWordCell.test.js')) +}) + +test('creates a cell component with a multi word name', () => { + expect( + multiWordFiles[ + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + ] + ).toEqual(loadGeneratorFixture('cell', 'multiWordCell.js')) +}) + +test('creates a cell test with a multi word name', () => { + expect( + multiWordFiles[ + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.test.js' + ] + ).toEqual(loadGeneratorFixture('cell', 'multiWordCell.test.js')) +}) diff --git a/packages/cli/src/commands/generate/cell/__tests__/fixtures/multiWordCell.js b/packages/cli/src/commands/generate/cell/__tests__/fixtures/multiWordCell.js new file mode 100644 index 000000000000..ed4e314357d2 --- /dev/null +++ b/packages/cli/src/commands/generate/cell/__tests__/fixtures/multiWordCell.js @@ -0,0 +1,17 @@ +export const QUERY = gql` + query { + userProfile { + id + } + } +` + +export const Loading = () =>
Loading...
+ +export const Empty = () =>
Empty
+ +export const Failure = ({ error }) =>
Error: {error.message}
+ +export const Success = ({ userProfile }) => { + return JSON.stringify(userProfile) +} diff --git a/packages/cli/src/commands/generate/cell/__tests__/fixtures/multiWordCell.test.js b/packages/cli/src/commands/generate/cell/__tests__/fixtures/multiWordCell.test.js new file mode 100644 index 000000000000..3272637ae59b --- /dev/null +++ b/packages/cli/src/commands/generate/cell/__tests__/fixtures/multiWordCell.test.js @@ -0,0 +1,34 @@ +import { render, cleanup, screen } from '@testing-library/react' + +import { Loading, Empty, Failure, Success } from './UserProfileCell' + +describe('UserProfileCell', () => { + afterEach(() => { + cleanup() + }) + + it('Loading renders successfully', () => { + render() + // Use screen.debug() to see output. + expect(screen.queryByText('Loading...')).toBeInTheDocument() + }) + + it('Empty renders successfully', () => { + render() + expect(screen.queryByText('Empty')).toBeInTheDocument() + }) + + it('Failure renders successfully', () => { + render() + expect(screen.queryByText('Error: Oh no!')).toBeInTheDocument() + }) + + it('Success renders successfully', () => { + render( + + ) + expect( + screen.queryByText('{"userProfile":{"objectKey":"objectValue"}}') + ).toBeInTheDocument() + }) +}) diff --git a/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.js b/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.js new file mode 100644 index 000000000000..308ceafe3ef5 --- /dev/null +++ b/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.js @@ -0,0 +1,17 @@ +export const QUERY = gql` + query { + user { + id + } + } +` + +export const Loading = () =>
Loading...
+ +export const Empty = () =>
Empty
+ +export const Failure = ({ error }) =>
Error: {error.message}
+ +export const Success = ({ user }) => { + return JSON.stringify(user) +} diff --git a/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.test.js b/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.test.js new file mode 100644 index 000000000000..c37966642feb --- /dev/null +++ b/packages/cli/src/commands/generate/cell/__tests__/fixtures/singleWordCell.test.js @@ -0,0 +1,34 @@ +import { render, cleanup, screen } from '@testing-library/react' + +import { Loading, Empty, Failure, Success } from './UserCell' + +describe('UserCell', () => { + afterEach(() => { + cleanup() + }) + + it('Loading renders successfully', () => { + render() + // Use screen.debug() to see output. + expect(screen.queryByText('Loading...')).toBeInTheDocument() + }) + + it('Empty renders successfully', () => { + render() + expect(screen.queryByText('Empty')).toBeInTheDocument() + }) + + it('Failure renders successfully', () => { + render() + expect(screen.queryByText('Error: Oh no!')).toBeInTheDocument() + }) + + it('Success renders successfully', () => { + render( + + ) + expect( + screen.queryByText('{"user":{"objectKey":"objectValue"}}') + ).toBeInTheDocument() + }) +}) diff --git a/packages/cli/src/commands/generate/commands/cell.js b/packages/cli/src/commands/generate/cell/cell.js similarity index 88% rename from packages/cli/src/commands/generate/commands/cell.js rename to packages/cli/src/commands/generate/cell/cell.js index 83c68b2c029a..247d9240009b 100644 --- a/packages/cli/src/commands/generate/commands/cell.js +++ b/packages/cli/src/commands/generate/cell/cell.js @@ -11,14 +11,16 @@ export const files = ({ name }) => { name, suffix: COMPONENT_SUFFIX, webPathSection: REDWOOD_WEB_PATH_NAME, - templatePath: 'cell/cell.js.template', + generator: 'cell', + templatePath: 'cell.js.template', }) const testFile = templateForComponentFile({ name, suffix: COMPONENT_SUFFIX, extension: '.test.js', webPathSection: REDWOOD_WEB_PATH_NAME, - templatePath: 'cell/test.js.template', + generator: 'cell', + templatePath: 'test.js.template', }) // Returns diff --git a/packages/cli/src/commands/generate/templates/cell/cell.js.template b/packages/cli/src/commands/generate/cell/templates/cell.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/cell/cell.js.template rename to packages/cli/src/commands/generate/cell/templates/cell.js.template diff --git a/packages/cli/src/commands/generate/templates/cell/test.js.template b/packages/cli/src/commands/generate/cell/templates/test.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/cell/test.js.template rename to packages/cli/src/commands/generate/cell/templates/test.js.template diff --git a/packages/cli/src/commands/generate/component/__tests__/component.test.js b/packages/cli/src/commands/generate/component/__tests__/component.test.js new file mode 100644 index 000000000000..4aeb62ec14cb --- /dev/null +++ b/packages/cli/src/commands/generate/component/__tests__/component.test.js @@ -0,0 +1,43 @@ +global.__dirname = __dirname +import { loadGeneratorFixture } from 'src/lib/test' + +import * as component from '../component' + +let singleWordFiles, multiWordFiles + +beforeAll(() => { + singleWordFiles = component.files({ name: 'User' }) + multiWordFiles = component.files({ name: 'UserProfile' }) +}) + +test('returns exactly 2 files', () => { + expect(Object.keys(singleWordFiles).length).toEqual(2) +}) + +test('creates a single word component', () => { + expect( + singleWordFiles['/path/to/project/web/src/components/User/User.js'] + ).toEqual(loadGeneratorFixture('component', 'singleWordComponent.js')) +}) + +test('creates a single word component test', () => { + expect( + singleWordFiles['/path/to/project/web/src/components/User/User.test.js'] + ).toEqual(loadGeneratorFixture('component', 'singleWordComponent.test.js')) +}) + +test('creates a multi word component', () => { + expect( + multiWordFiles[ + '/path/to/project/web/src/components/UserProfile/UserProfile.js' + ] + ).toEqual(loadGeneratorFixture('component', 'multiWordComponent.js')) +}) + +test('creates a multi word component test', () => { + expect( + multiWordFiles[ + '/path/to/project/web/src/components/UserProfile/UserProfile.test.js' + ] + ).toEqual(loadGeneratorFixture('component', 'multiWordComponent.test.js')) +}) diff --git a/packages/cli/src/commands/generate/component/__tests__/fixtures/multiWordComponent.js b/packages/cli/src/commands/generate/component/__tests__/fixtures/multiWordComponent.js new file mode 100644 index 000000000000..8d0f41331dad --- /dev/null +++ b/packages/cli/src/commands/generate/component/__tests__/fixtures/multiWordComponent.js @@ -0,0 +1,10 @@ +const UserProfile = () => { + return ( +
+

{'UserProfile'}

+

{'Find me in ./web/src/components/UserProfile/UserProfile.js'}

+
+ ) +} + +export default UserProfile diff --git a/packages/cli/src/commands/generate/component/__tests__/fixtures/multiWordComponent.test.js b/packages/cli/src/commands/generate/component/__tests__/fixtures/multiWordComponent.test.js new file mode 100644 index 000000000000..71375ef0db1e --- /dev/null +++ b/packages/cli/src/commands/generate/component/__tests__/fixtures/multiWordComponent.test.js @@ -0,0 +1,14 @@ +import { render, cleanup } from '@testing-library/react' + +import UserProfile from './UserProfile' + +describe('UserProfile', () => { + afterEach(() => { + cleanup() + }) + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/packages/cli/src/commands/generate/component/__tests__/fixtures/singleWordComponent.js b/packages/cli/src/commands/generate/component/__tests__/fixtures/singleWordComponent.js new file mode 100644 index 000000000000..77e63848bd0d --- /dev/null +++ b/packages/cli/src/commands/generate/component/__tests__/fixtures/singleWordComponent.js @@ -0,0 +1,10 @@ +const User = () => { + return ( +
+

{'User'}

+

{'Find me in ./web/src/components/User/User.js'}

+
+ ) +} + +export default User diff --git a/packages/cli/src/commands/generate/component/__tests__/fixtures/singleWordComponent.test.js b/packages/cli/src/commands/generate/component/__tests__/fixtures/singleWordComponent.test.js new file mode 100644 index 000000000000..05a6ba2554e8 --- /dev/null +++ b/packages/cli/src/commands/generate/component/__tests__/fixtures/singleWordComponent.test.js @@ -0,0 +1,14 @@ +import { render, cleanup } from '@testing-library/react' + +import User from './User' + +describe('User', () => { + afterEach(() => { + cleanup() + }) + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/packages/cli/src/commands/generate/commands/component.js b/packages/cli/src/commands/generate/component/component.js similarity index 85% rename from packages/cli/src/commands/generate/commands/component.js rename to packages/cli/src/commands/generate/component/component.js index eadca1c64ba4..19edeaa80c8e 100644 --- a/packages/cli/src/commands/generate/commands/component.js +++ b/packages/cli/src/commands/generate/component/component.js @@ -9,13 +9,15 @@ export const files = ({ name }) => { const componentFile = templateForComponentFile({ name, webPathSection: REDWOOD_WEB_PATH_NAME, - templatePath: 'component/component.js.template', + generator: 'component', + templatePath: 'component.js.template', }) const testFile = templateForComponentFile({ name, extension: '.test.js', webPathSection: REDWOOD_WEB_PATH_NAME, - templatePath: 'component/test.js.template', + generator: 'component', + templatePath: 'test.js.template', }) // Returns diff --git a/packages/cli/src/commands/generate/templates/component/component.js.template b/packages/cli/src/commands/generate/component/templates/component.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/component/component.js.template rename to packages/cli/src/commands/generate/component/templates/component.js.template diff --git a/packages/cli/src/commands/generate/templates/component/test.js.template b/packages/cli/src/commands/generate/component/templates/test.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/component/test.js.template rename to packages/cli/src/commands/generate/component/templates/test.js.template diff --git a/packages/cli/src/commands/generate/helpers.js b/packages/cli/src/commands/generate/helpers.js index 59adf8920fea..eab795921383 100644 --- a/packages/cli/src/commands/generate/helpers.js +++ b/packages/cli/src/commands/generate/helpers.js @@ -2,6 +2,7 @@ import path from 'path' import Listr from 'listr' import pascalcase from 'pascalcase' +import { paramCase } from 'param-case' import { generateTemplate, getPaths, writeFilesTask } from 'src/lib' import c from 'src/lib/colors' @@ -18,6 +19,7 @@ export const templateForComponentFile = ({ extension = '.js', webPathSection, apiPathSection, + generator, templatePath, templateVars, componentName, @@ -25,20 +27,32 @@ export const templateForComponentFile = ({ const basePath = webPathSection ? getPaths().web[webPathSection] : getPaths().api[apiPathSection] - const outputComponentName = componentName || pascalcase(name) + suffix + const outputComponentName = + componentName || pascalcase(paramCase(name)) + suffix const outputPath = path.join( basePath, outputComponentName, outputComponentName + extension ) - const content = generateTemplate(templatePath, { - name, - outputPath: `./${path.relative(getPaths().base, outputPath)}`, - ...templateVars, - }) + const content = generateTemplate( + path.join(generator, 'templates', templatePath), + { + name, + outputPath: `./${path.relative(getPaths().base, outputPath)}`, + ...templateVars, + } + ) return [outputPath, content] } +/** + * Creates a route path, either returning the existing path if passed, otherwise + * creates one based on the name + */ +export const pathName = (path, name) => { + return path ?? `/${paramCase(name)}` +} + /** * Reduces boilerplate for creating a yargs handler that writes a component to a * location. diff --git a/packages/cli/src/commands/generate/layout/__tests__/fixtures/multiWordLayout.js b/packages/cli/src/commands/generate/layout/__tests__/fixtures/multiWordLayout.js new file mode 100644 index 000000000000..12417975266c --- /dev/null +++ b/packages/cli/src/commands/generate/layout/__tests__/fixtures/multiWordLayout.js @@ -0,0 +1,5 @@ +const SinglePageLayout = ({ children }) => { + return <>{children} +} + +export default SinglePageLayout diff --git a/packages/cli/src/commands/generate/layout/__tests__/fixtures/multiWordLayout.test.js b/packages/cli/src/commands/generate/layout/__tests__/fixtures/multiWordLayout.test.js new file mode 100644 index 000000000000..28fc332ee9d0 --- /dev/null +++ b/packages/cli/src/commands/generate/layout/__tests__/fixtures/multiWordLayout.test.js @@ -0,0 +1,14 @@ +import { render, cleanup } from '@testing-library/react' + +import SinglePageLayout from './SinglePageLayout' + +describe('SinglePageLayout', () => { + afterEach(() => { + cleanup() + }) + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/packages/cli/src/commands/generate/layout/__tests__/fixtures/singleWordLayout.js b/packages/cli/src/commands/generate/layout/__tests__/fixtures/singleWordLayout.js new file mode 100644 index 000000000000..df0cefbc948f --- /dev/null +++ b/packages/cli/src/commands/generate/layout/__tests__/fixtures/singleWordLayout.js @@ -0,0 +1,5 @@ +const AppLayout = ({ children }) => { + return <>{children} +} + +export default AppLayout diff --git a/packages/cli/src/commands/generate/layout/__tests__/fixtures/singleWordLayout.test.js b/packages/cli/src/commands/generate/layout/__tests__/fixtures/singleWordLayout.test.js new file mode 100644 index 000000000000..e095a5bb5abc --- /dev/null +++ b/packages/cli/src/commands/generate/layout/__tests__/fixtures/singleWordLayout.test.js @@ -0,0 +1,14 @@ +import { render, cleanup } from '@testing-library/react' + +import AppLayout from './AppLayout' + +describe('AppLayout', () => { + afterEach(() => { + cleanup() + }) + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/packages/cli/src/commands/generate/layout/__tests__/layout.test.js b/packages/cli/src/commands/generate/layout/__tests__/layout.test.js new file mode 100644 index 000000000000..9b7ba9b34604 --- /dev/null +++ b/packages/cli/src/commands/generate/layout/__tests__/layout.test.js @@ -0,0 +1,45 @@ +global.__dirname = __dirname +import { loadGeneratorFixture } from 'src/lib/test' + +import * as layout from '../layout' + +let singleWordFiles, multiWordFiles + +beforeAll(() => { + singleWordFiles = layout.files({ name: 'App' }) + multiWordFiles = layout.files({ name: 'SinglePage' }) +}) + +test('returns exactly 2 files', () => { + expect(Object.keys(singleWordFiles).length).toEqual(2) +}) + +test('creates a single word layout component', () => { + expect( + singleWordFiles['/path/to/project/web/src/layouts/AppLayout/AppLayout.js'] + ).toEqual(loadGeneratorFixture('layout', 'singleWordLayout.js')) +}) + +test('creates a single word layout test', () => { + expect( + singleWordFiles[ + '/path/to/project/web/src/layouts/AppLayout/AppLayout.test.js' + ] + ).toEqual(loadGeneratorFixture('layout', 'singleWordLayout.test.js')) +}) + +test('creates a multi word layout component', () => { + expect( + multiWordFiles[ + '/path/to/project/web/src/layouts/SinglePageLayout/SinglePageLayout.js' + ] + ).toEqual(loadGeneratorFixture('layout', 'multiWordLayout.js')) +}) + +test('creates a multi word layout test', () => { + expect( + multiWordFiles[ + '/path/to/project/web/src/layouts/SinglePageLayout/SinglePageLayout.test.js' + ] + ).toEqual(loadGeneratorFixture('layout', 'multiWordLayout.test.js')) +}) diff --git a/packages/cli/src/commands/generate/commands/layout.js b/packages/cli/src/commands/generate/layout/layout.js similarity index 87% rename from packages/cli/src/commands/generate/commands/layout.js rename to packages/cli/src/commands/generate/layout/layout.js index 6fb8cd8ef91e..58ec9feefb2e 100644 --- a/packages/cli/src/commands/generate/commands/layout.js +++ b/packages/cli/src/commands/generate/layout/layout.js @@ -11,14 +11,16 @@ export const files = ({ name }) => { name, suffix: COMPONENT_SUFFIX, webPathSection: REDWOOD_WEB_PATH_NAME, - templatePath: 'layout/layout.js.template', + generator: 'layout', + templatePath: 'layout.js.template', }) const testFile = templateForComponentFile({ name, suffix: COMPONENT_SUFFIX, extension: '.test.js', webPathSection: REDWOOD_WEB_PATH_NAME, - templatePath: 'layout/test.js.template', + generator: 'layout', + templatePath: 'test.js.template', }) // Returns diff --git a/packages/cli/src/commands/generate/templates/layout/layout.js.template b/packages/cli/src/commands/generate/layout/templates/layout.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/layout/layout.js.template rename to packages/cli/src/commands/generate/layout/templates/layout.js.template diff --git a/packages/cli/src/commands/generate/templates/layout/test.js.template b/packages/cli/src/commands/generate/layout/templates/test.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/layout/test.js.template rename to packages/cli/src/commands/generate/layout/templates/test.js.template diff --git a/packages/cli/src/commands/generate/page/__tests__/fixtures/multiWordPage.js b/packages/cli/src/commands/generate/page/__tests__/fixtures/multiWordPage.js new file mode 100644 index 000000000000..3b6174d847f5 --- /dev/null +++ b/packages/cli/src/commands/generate/page/__tests__/fixtures/multiWordPage.js @@ -0,0 +1,10 @@ +const ContactUsPage = () => { + return ( +
+

ContactUsPage

+

Find me in ./web/src/pages/ContactUsPage/ContactUsPage.js

+
+ ) +} + +export default ContactUsPage diff --git a/packages/cli/src/commands/generate/page/__tests__/fixtures/multiWordPage.test.js b/packages/cli/src/commands/generate/page/__tests__/fixtures/multiWordPage.test.js new file mode 100644 index 000000000000..2646d375cf18 --- /dev/null +++ b/packages/cli/src/commands/generate/page/__tests__/fixtures/multiWordPage.test.js @@ -0,0 +1,14 @@ +import { render, cleanup } from '@testing-library/react' + +import ContactUsPage from './ContactUsPage' + +describe('ContactUsPage', () => { + afterEach(() => { + cleanup() + }) + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/packages/cli/src/commands/generate/page/__tests__/fixtures/singleWordPage.js b/packages/cli/src/commands/generate/page/__tests__/fixtures/singleWordPage.js new file mode 100644 index 000000000000..7fa116a2507e --- /dev/null +++ b/packages/cli/src/commands/generate/page/__tests__/fixtures/singleWordPage.js @@ -0,0 +1,10 @@ +const HomePage = () => { + return ( +
+

HomePage

+

Find me in ./web/src/pages/HomePage/HomePage.js

+
+ ) +} + +export default HomePage diff --git a/packages/cli/src/commands/generate/page/__tests__/fixtures/singleWordPage.test.js b/packages/cli/src/commands/generate/page/__tests__/fixtures/singleWordPage.test.js new file mode 100644 index 000000000000..129f9ff2c068 --- /dev/null +++ b/packages/cli/src/commands/generate/page/__tests__/fixtures/singleWordPage.test.js @@ -0,0 +1,14 @@ +import { render, cleanup } from '@testing-library/react' + +import HomePage from './HomePage' + +describe('HomePage', () => { + afterEach(() => { + cleanup() + }) + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/packages/cli/src/commands/generate/page/__tests__/page.test.js b/packages/cli/src/commands/generate/page/__tests__/page.test.js new file mode 100644 index 000000000000..1c6eb50cd7ca --- /dev/null +++ b/packages/cli/src/commands/generate/page/__tests__/page.test.js @@ -0,0 +1,69 @@ +global.__dirname = __dirname +import { loadGeneratorFixture } from 'src/lib/test' + +import * as page from '../page' + +let singleWordFiles, multiWordFiles + +beforeAll(() => { + singleWordFiles = page.files({ name: 'Home' }) + multiWordFiles = page.files({ name: 'ContactUs' }) +}) + +test('returns exactly 2 files', () => { + expect(Object.keys(singleWordFiles).length).toEqual(2) +}) + +test('creates a page component', () => { + expect( + singleWordFiles['/path/to/project/web/src/pages/HomePage/HomePage.js'] + ).toEqual(loadGeneratorFixture('page', 'singleWordPage.js')) +}) + +test('creates a page test', () => { + expect( + singleWordFiles['/path/to/project/web/src/pages/HomePage/HomePage.test.js'] + ).toEqual(loadGeneratorFixture('page', 'singleWordPage.test.js')) +}) + +test('creates a page component', () => { + expect( + multiWordFiles[ + '/path/to/project/web/src/pages/ContactUsPage/ContactUsPage.js' + ] + ).toEqual(loadGeneratorFixture('page', 'multiWordPage.js')) +}) + +test('creates a page test', () => { + expect( + multiWordFiles[ + '/path/to/project/web/src/pages/ContactUsPage/ContactUsPage.test.js' + ] + ).toEqual(loadGeneratorFixture('page', 'multiWordPage.test.js')) +}) + +test('creates a single-word route name', () => { + const names = ['Home', 'home'] + + names.forEach((name) => { + expect(page.routes({ name: name, path: 'home' })).toEqual([ + '', + ]) + }) +}) + +test('creates a camelCase route name for multiple word names', () => { + const names = ['FooBar', 'foo_bar', 'foo-bar', 'fooBar'] + + names.forEach((name) => { + expect(page.routes({ name: name, path: 'foo-bar' })).toEqual([ + '', + ]) + }) +}) + +test('creates a path equal to passed path', () => { + expect(page.routes({ name: 'FooBar', path: 'fooBar-baz' })).toEqual([ + '', + ]) +}) diff --git a/packages/cli/src/commands/generate/commands/page.js b/packages/cli/src/commands/generate/page/page.js similarity index 83% rename from packages/cli/src/commands/generate/commands/page.js rename to packages/cli/src/commands/generate/page/page.js index cb3d9a56a284..17332ee15ac2 100644 --- a/packages/cli/src/commands/generate/commands/page.js +++ b/packages/cli/src/commands/generate/page/page.js @@ -1,12 +1,11 @@ import Listr from 'listr' import camelcase from 'camelcase' import pascalcase from 'pascalcase' -import { paramCase } from 'param-case' import { writeFilesTask, addRoutesToRouterTask } from 'src/lib' import c from 'src/lib/colors' -import { templateForComponentFile } from '../helpers' +import { templateForComponentFile, pathName } from '../helpers' const COMPONENT_SUFFIX = 'Page' const REDWOOD_WEB_PATH_NAME = 'pages' @@ -16,7 +15,8 @@ export const files = ({ name, ...rest }) => { name, suffix: COMPONENT_SUFFIX, webPathSection: REDWOOD_WEB_PATH_NAME, - templatePath: 'page/page.js.template', + generator: 'page', + templatePath: 'page.js.template', templateVars: rest, }) const testFile = templateForComponentFile({ @@ -24,7 +24,8 @@ export const files = ({ name, ...rest }) => { suffix: COMPONENT_SUFFIX, extension: '.test.js', webPathSection: REDWOOD_WEB_PATH_NAME, - templatePath: 'page/test.js.template', + generator: 'page', + templatePath: 'test.js.template', templateVars: rest, }) @@ -54,20 +55,19 @@ export const desc = 'Generates a page component.' export const builder = { force: { type: 'boolean', default: false } } export const handler = async ({ name, path, force }) => { - path = path ?? `/${paramCase(name)}` const tasks = new Listr( [ { title: 'Generating page files...', task: async () => { - const f = await files({ name, path }) + const f = await files({ name, path: pathName(path, name) }) return writeFilesTask(f, { overwriteExisting: force }) }, }, { title: 'Updating routes file...', task: async () => { - addRoutesToRouterTask(routes({ name, path })) + addRoutesToRouterTask(routes({ name, path: pathName(path, name) })) }, }, ].filter(Boolean), diff --git a/packages/cli/src/commands/generate/templates/page/page.js.template b/packages/cli/src/commands/generate/page/templates/page.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/page/page.js.template rename to packages/cli/src/commands/generate/page/templates/page.js.template diff --git a/packages/cli/src/commands/generate/templates/page/test.js.template b/packages/cli/src/commands/generate/page/templates/test.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/page/test.js.template rename to packages/cli/src/commands/generate/page/templates/test.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/assets/scaffold.css.template b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/assets/scaffold.css similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/assets/scaffold.css.template rename to packages/cli/src/commands/generate/scaffold/__tests__/fixtures/assets/scaffold.css diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/editCell.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/editCell.js new file mode 100644 index 000000000000..b0d0f41e62e0 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/editCell.js @@ -0,0 +1,49 @@ +import { useMutation } from '@redwoodjs/web' +import { navigate, routes } from '@redwoodjs/router' +import PostForm from 'src/components/PostForm' + +export const QUERY = gql` + query FIND_POST_BY_ID($id: Int!) { + post: post(id: $id) { + id + title + slug + author + body + image + postedAt + } + } +` +const UPDATE_POST_MUTATION = gql` + mutation UpdatePostMutation($id: Int!, $input: PostInput!) { + updatePost(id: $id, input: $input) { + id + } + } +` + +export const Loading = () =>
Loading...
+ +export const Success = ({ post }) => { + const [updatePost, { loading, error }] = useMutation(UPDATE_POST_MUTATION, { + onCompleted: () => { + navigate(routes.posts()) + }, + }) + + const onSave = (input, id) => { + updatePost({ variables: { id, input } }) + } + + return ( +
+
+

Edit Post {post.id}

+
+
+ +
+
+ ) +} diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/form.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/form.js new file mode 100644 index 000000000000..e2e0fada5d46 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/form.js @@ -0,0 +1,132 @@ +import { + Form, + FormError, + FieldError, + Label, + TextField, + Submit, +} from '@redwoodjs/web' + +const CSS = { + label: 'block mt-6 text-gray-700 font-semibold', + labelError: 'block mt-6 font-semibold text-red-700', + input: + 'block mt-2 w-full p-2 border border-gray-300 text-gray-700 rounded focus:outline-none focus:border-gray-500', + inputError: + 'block mt-2 w-full p-2 border border-red-700 text-red-900 rounded focus:outline-none', + errorMessage: 'block mt-1 font-semibold uppercase text-xs text-red-700', +} + +const PostForm = (props) => { + const onSubmit = (data) => { + props.onSave(data, props?.post?.id) + } + + return ( +
+
+ + +
+ ) +} + +export default PostForm diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/index.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/index.js new file mode 100644 index 000000000000..43f45c93da47 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/index.js @@ -0,0 +1,113 @@ +import { useMutation } from '@redwoodjs/web' +import { Link, routes } from '@redwoodjs/router' + +const DELETE_POST_MUTATION = gql` + mutation DeletePostMutation($id: Int!) { + deletePost(id: $id) { + id + } + } +` + +const MAX_STRING_LENGTH = 150 + +const truncate = (text) => { + let output = text + if (text.length > MAX_STRING_LENGTH) { + output = output.substring(0, MAX_STRING_LENGTH) + '...' + } + return output +} + +const timeTag = (datetime) => { + return ( + + ) +} + +const PostsList = ({ posts }) => { + const [deletePost] = useMutation(DELETE_POST_MUTATION, { + onCompleted: () => { + location.reload() + }, + }) + + const onDeleteClick = (id) => { + if (confirm('Are you sure you want to delete post ' + id + '?')) { + deletePost({ variables: { id } }) + } + } + + return ( +
+ + + + + + + + + + + + + + + {posts.map((post) => ( + + + + + + + + + + + ))} + +
idtitleslugauthorbodyimagepostedAt 
{truncate(post.id)}{truncate(post.title)}{truncate(post.slug)}{truncate(post.author)}{truncate(post.body)}{truncate(post.image)}{timeTag(post.postedAt)} + +
+
+ ) +} + +export default PostsList diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/indexCell.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/indexCell.js new file mode 100644 index 000000000000..e799cb6ac0f6 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/indexCell.js @@ -0,0 +1,41 @@ +import { Link, routes } from '@redwoodjs/router' + +import Posts from 'src/components/Posts' + +export const QUERY = gql` + query POSTS { + posts { + id + title + slug + author + body + image + postedAt + } + } +` + +export const beforeQuery = (props) => { + return { variables: props, fetchPolicy: 'cache-and-network' } +} + +export const Loading = () =>
Loading...
+ +export const Empty = () => { + return ( +
+ {'No posts yet. '} + + {'Create one?'} + +
+ ) +} + +export const Success = ({ posts }) => { + return +} diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/new.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/new.js new file mode 100644 index 000000000000..cc3509c6ccf1 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/new.js @@ -0,0 +1,36 @@ +import { useMutation } from '@redwoodjs/web' +import { navigate, routes } from '@redwoodjs/router' +import PostForm from 'src/components/PostForm' + +const CREATE_POST_MUTATION = gql` + mutation CreatePostMutation($input: PostInput!) { + createPost(input: $input) { + id + } + } +` + +const NewPost = () => { + const [createPost, { loading, error }] = useMutation(CREATE_POST_MUTATION, { + onCompleted: () => { + navigate(routes.posts()) + }, + }) + + const onSave = (input) => { + createPost({ variables: { input } }) + } + + return ( +
+
+

New Post

+
+
+ +
+
+ ) +} + +export default NewPost diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/show.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/show.js new file mode 100644 index 000000000000..cccb15046af6 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/show.js @@ -0,0 +1,84 @@ +import { useMutation } from '@redwoodjs/web' +import { Link, routes, navigate } from '@redwoodjs/router' + +const DELETE_POST_MUTATION = gql` + mutation DeletePostMutation($id: Int!) { + deletePost(id: $id) { + id + } + } +` + +const Post = ({ post }) => { + const [deletePost] = useMutation(DELETE_POST_MUTATION, { + onCompleted: () => { + navigate(routes.posts()) + location.reload() + }, + }) + + const onDeleteClick = (id) => { + if (confirm('Are you sure you want to delete post ' + id + '?')) { + deletePost({ variables: { id } }) + } + } + + return ( + <> +
+
+

Post {post.id} Detail

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
id{post.id}
title{post.title}
slug{post.slug}
author{post.author}
body{post.body}
image{post.image}
postedAt{post.postedAt}
+
+ + + ) +} + +export default Post diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/showCell.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/showCell.js new file mode 100644 index 000000000000..0fe5636ae4f2 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/showCell.js @@ -0,0 +1,23 @@ +import Post from 'src/components/Post' + +export const QUERY = gql` + query FIND_POST_BY_ID($id: Int!) { + post: post(id: $id) { + id + title + slug + author + body + image + postedAt + } + } +` + +export const Loading = () =>
Loading...
+ +export const Empty = () =>
Post not found
+ +export const Success = ({ post }) => { + return +} diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/layouts/layout.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/layouts/layout.js new file mode 100644 index 000000000000..363fdabce86c --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/layouts/layout.js @@ -0,0 +1,30 @@ +import { Link, routes } from '@redwoodjs/router' + +const PostsLayout = (props) => { + return ( +
+
+
+

+ + Posts + +

+ +
+
+
New Post
+ +
+
{props.children}
+
+
+ ) +} + +export default PostsLayout diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/editPage.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/editPage.js new file mode 100644 index 000000000000..419af6bb99e8 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/editPage.js @@ -0,0 +1,12 @@ +import PostsLayout from 'src/layouts/PostsLayout' +import EditPostCell from 'src/components/EditPostCell' + +const EditPostPage = ({ id }) => { + return ( + + + + ) +} + +export default EditPostPage diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/indexPage.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/indexPage.js new file mode 100644 index 000000000000..7318d9bfa825 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/indexPage.js @@ -0,0 +1,12 @@ +import PostsLayout from 'src/layouts/PostsLayout' +import PostsCell from 'src/components/PostsCell' + +const PostsPage = () => { + return ( + + + + ) +} + +export default PostsPage diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/newPage.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/newPage.js new file mode 100644 index 000000000000..7a41350d0c10 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/newPage.js @@ -0,0 +1,12 @@ +import PostsLayout from 'src/layouts/PostsLayout' +import NewPost from 'src/components/NewPost' + +const NewPostPage = () => { + return ( + + + + ) +} + +export default NewPostPage diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/showPage.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/showPage.js new file mode 100644 index 000000000000..8b9eec35b129 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/pages/showPage.js @@ -0,0 +1,12 @@ +import PostsLayout from 'src/layouts/PostsLayout' +import PostCell from 'src/components/PostCell' + +const PostPage = ({ id }) => { + return ( + + + + ) +} + +export default PostPage diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/schema.prisma b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/schema.prisma new file mode 100644 index 000000000000..83bba6ebc77a --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/schema.prisma @@ -0,0 +1,36 @@ +datasource hammerDatasource { + provider = "sqlite" + url = env("DB_HOST") +} + +generator client { + provider = "prisma-client-js" +} + +// Define your own models here and run yarn db:save to create +// migrations for them. + +model Post { + id Int @id @default(autoincrement()) + title String + slug String @unique + author String + body String + image String? + postedAt DateTime? +} + +model User { + id Int @id @default(autoincrement()) + name String? + email String @unique + isAdmin Boolean @default(false) + profiles UserProfile[] +} + +model UserProfile { + id Int @id @default(autoincrement()) + username String @unique + userId Int + user User @relation(fields: [userId], references: [id]) +} diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js b/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js new file mode 100644 index 000000000000..bc36bf5b41ec --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js @@ -0,0 +1,163 @@ +global.__dirname = __dirname +import path from 'path' + +import { + loadFixture, + loadGeneratorFixture, + sdlFixturesPath, + serviceFixturesPath, +} from 'src/lib/test' + +import * as scaffold from '../scaffold' + +let files + +beforeAll(async () => { + files = await scaffold.files({ model: 'Post' }) +}) + +test('returns exactly 16 files', () => { + expect(Object.keys(files).length).toEqual(16) +}) + +// styles + +test('creates a stylesheet', () => { + expect(files['/path/to/project/web/src/scaffold.css']).toEqual( + loadGeneratorFixture('scaffold', path.join('assets', 'scaffold.css')) + ) +}) + +// SDL + +test('creates a graphql sdl', () => { + expect(files['/path/to/project/api/src/graphql/posts.sdl.js']).toEqual( + loadFixture(path.join(sdlFixturesPath, 'singleWordSdlCrud.js')) + ) +}) + +// Service + +test('creates a service', () => { + expect(files['/path/to/project/api/src/services/posts/posts.js']).toEqual( + loadFixture(path.join(serviceFixturesPath, 'singleWordServiceCrud.js')) + ) +}) + +test('creates a service test', () => { + expect( + files['/path/to/project/api/src/services/posts/posts.test.js'] + ).toEqual( + loadFixture(path.join(serviceFixturesPath, 'singleWordServiceCrud.test.js')) + ) +}) + +// Layout + +test('creates a layout', async () => { + expect( + files['/path/to/project/web/src/layouts/PostsLayout/PostsLayout.js'] + ).toEqual(loadGeneratorFixture('scaffold', path.join('layouts', 'layout.js'))) +}) + +// Pages + +test('creates a edit page', async () => { + expect( + files['/path/to/project/web/src/pages/EditPostPage/EditPostPage.js'] + ).toEqual(loadGeneratorFixture('scaffold', path.join('pages', 'editPage.js'))) +}) + +test('creates a index page', async () => { + expect( + files['/path/to/project/web/src/pages/PostsPage/PostsPage.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'indexPage.js')) + ) +}) + +test('creates a new page', async () => { + expect( + files['/path/to/project/web/src/pages/NewPostPage/NewPostPage.js'] + ).toEqual(loadGeneratorFixture('scaffold', path.join('pages', 'newPage.js'))) +}) + +test('creates a show page', async () => { + expect(files['/path/to/project/web/src/pages/PostPage/PostPage.js']).toEqual( + loadGeneratorFixture('scaffold', path.join('pages', 'showPage.js')) + ) +}) + +// Cells + +test('creates an edit cell', async () => { + expect( + files['/path/to/project/web/src/components/EditPostCell/EditPostCell.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'editCell.js')) + ) +}) + +test('creates an index cell', async () => { + expect( + files['/path/to/project/web/src/components/PostsCell/PostsCell.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'indexCell.js')) + ) +}) + +test('creates a show cell', async () => { + expect( + files['/path/to/project/web/src/components/PostCell/PostCell.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'showCell.js')) + ) +}) + +// Components + +test('creates a form component', async () => { + expect( + files['/path/to/project/web/src/components/PostForm/PostForm.js'] + ).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'form.js')) + ) +}) + +test('creates an index component', async () => { + expect(files['/path/to/project/web/src/components/Posts/Posts.js']).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'index.js')) + ) +}) + +test('creates a new component', async () => { + expect( + files['/path/to/project/web/src/components/NewPost/NewPost.js'] + ).toEqual(loadGeneratorFixture('scaffold', path.join('components', 'new.js'))) +}) + +test('creates a show component', async () => { + expect(files['/path/to/project/web/src/components/Post/Post.js']).toEqual( + loadGeneratorFixture('scaffold', path.join('components', 'show.js')) + ) +}) + +// Routes + +test('creates a single-word name routes', async () => { + expect(await scaffold.routes({ model: 'Post' })).toEqual([ + '', + '', + '', + '', + ]) +}) + +test('creates a multi-word name routes', async () => { + expect(await scaffold.routes({ model: 'UserProfile' })).toEqual([ + '', + '', + '', + '', + ]) +}) diff --git a/packages/cli/src/commands/generate/commands/scaffold.js b/packages/cli/src/commands/generate/scaffold/scaffold.js similarity index 82% rename from packages/cli/src/commands/generate/commands/scaffold.js rename to packages/cli/src/commands/generate/scaffold/scaffold.js index 8b9f453aa384..e32a1dc0a052 100644 --- a/packages/cli/src/commands/generate/commands/scaffold.js +++ b/packages/cli/src/commands/generate/scaffold/scaffold.js @@ -5,6 +5,7 @@ import Listr from 'listr' import camelcase from 'camelcase' import pascalcase from 'pascalcase' import pluralize from 'pluralize' +import { paramCase } from 'param-case' import { generateTemplate, @@ -19,15 +20,21 @@ import { } from 'src/lib' import c from 'src/lib/colors' -import { files as sdlFiles } from './sdl' -import { files as serviceFiles } from './service' +import { files as sdlFiles } from '../sdl/sdl' +import { files as serviceFiles } from '../service/service' const NON_EDITABLE_COLUMNS = ['id', 'createdAt', 'updatedAt'] -const ASSETS = fs.readdirSync(path.join(templateRoot, 'scaffold', 'assets')) -const LAYOUTS = fs.readdirSync(path.join(templateRoot, 'scaffold', 'layouts')) -const PAGES = fs.readdirSync(path.join(templateRoot, 'scaffold', 'pages')) +const ASSETS = fs.readdirSync( + path.join(templateRoot, 'scaffold', 'templates', 'assets') +) +const LAYOUTS = fs.readdirSync( + path.join(templateRoot, 'scaffold', 'templates', 'layouts') +) +const PAGES = fs.readdirSync( + path.join(templateRoot, 'scaffold', 'templates', 'pages') +) const COMPONENTS = fs.readdirSync( - path.join(templateRoot, 'scaffold', 'components') + path.join(templateRoot, 'scaffold', 'templates', 'components') ) const SCAFFOLD_STYLE_PATH = './scaffold.css' // Any assets that should not trigger an overwrite error and require a --force @@ -61,7 +68,7 @@ const assetFiles = (name) => { !fs.existsSync(outputPath) ) { const template = generateTemplate( - path.join('scaffold', 'assets', asset), + path.join('scaffold', 'templates', 'assets', asset), { name, } @@ -89,7 +96,7 @@ const layoutFiles = (name) => { outputLayoutName ) const template = generateTemplate( - path.join('scaffold', 'layouts', layout), + path.join('scaffold', 'templates', 'layouts', layout), { name, } @@ -115,9 +122,12 @@ const pageFiles = (name) => { outputPageName.replace(/\.js/, ''), outputPageName ) - const template = generateTemplate(path.join('scaffold', 'pages', page), { - name, - }) + const template = generateTemplate( + path.join('scaffold', 'templates', 'pages', page), + { + name, + } + ) fileList[outputPath] = template }) @@ -147,7 +157,7 @@ const componentFiles = async (name) => { ) const template = generateTemplate( - path.join('scaffold', 'components', component), + path.join('scaffold', 'templates', 'components', component), { name, columns, @@ -162,19 +172,20 @@ const componentFiles = async (name) => { } // add routes for all pages -const routes = async ({ model: name }) => { +export const routes = async ({ model: name }) => { const singularPascalName = pascalcase(pluralize.singular(name)) const pluralPascalName = pascalcase(pluralize(name)) const singularCamelName = camelcase(singularPascalName) const pluralCamelName = camelcase(pluralPascalName) + const pluralParamName = paramCase(pluralPascalName) const model = await getSchema(singularPascalName) const idRouteParam = getIdType(model) === 'Int' ? ':Int' : '' return [ - ``, - ``, - ``, - ``, + ``, + ``, + ``, + ``, ] } diff --git a/packages/cli/src/commands/generate/scaffold/templates/assets/scaffold.css.template b/packages/cli/src/commands/generate/scaffold/templates/assets/scaffold.css.template new file mode 100644 index 000000000000..369fae2c5764 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/templates/assets/scaffold.css.template @@ -0,0 +1,167 @@ +/* + This file was created automatically as the result of the scaffold generator. + If you decide to use TailwindCSS in your project then you can safely delete + this file, but first remove the
from the Layout + that your scaffold is wrapped in. Check the comment blocks below for a + couple of config options you'll need to add to Tailwind to preserve the + scaffold appearance. + + If you don't use Tailwind then you should keep it here until you have + completely converted your scaffolded pages or removed them, or if you don't + mind your scaffolded pages being monumentally ugly. +*/ + +/* + normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +*/ + +.rw-scaffold main{display:block} +.rw-scaffold h1{font-size:2em;margin:.67em 0} +.rw-scaffold a{background-color:transparent} +.rw-scaffold input{font-family:inherit;font-size:100%;line-height:1.15;margin:0} +.rw-scaffold input{overflow:visible} +.rw-scaffold *,.rw-scaffold ::after,.rw-scaffold ::before{box-sizing:inherit} +.rw-scaffold h1,.rw-scaffold h2{margin:0} +.rw-scaffold ul{list-style:none;margin:0;padding:0} +.rw-scaffold *,.rw-scaffold ::after,.rw-scaffold ::before{border-width:0;border-style:solid;border-color:#e2e8f0} +.rw-scaffold input:-ms-input-placeholder{color:#a0aec0} +.rw-scaffold input::-ms-input-placeholder{color:#a0aec0} +.rw-scaffold input::placeholder{color:#a0aec0} +.rw-scaffold table{border-collapse:collapse} +.rw-scaffold h1,.rw-scaffold .rw-scaffold h2{font-size:inherit;font-weight:inherit} +.rw-scaffold a{color:inherit;text-decoration:inherit} +.rw-scaffold input{padding:0;line-height:inherit;color:inherit} + +/* + Classes we use in scaffolds from TailwindCSS v1.1.4 +*/ + +.rw-scaffold .bg-white{background-color:#fff} +.rw-scaffold .bg-gray-100{background-color:#f7fafc} +.rw-scaffold .bg-gray-300{background-color:#e2e8f0} +.rw-scaffold .bg-red-100{background-color:#fff5f5} +.rw-scaffold .bg-red-600{background-color:#e53e3e} +.rw-scaffold .bg-green-500{background-color:#48bb78} +.rw-scaffold .bg-blue-600{background-color:#3182ce} +.rw-scaffold .hover\:bg-gray-600:hover{background-color:#718096} +.rw-scaffold .hover\:bg-red-600:hover{background-color:#e53e3e} +.rw-scaffold .hover\:bg-red-700:hover{background-color:#c53030} +.rw-scaffold .hover\:bg-green-600:hover{background-color:#38a169} +.rw-scaffold .hover\:bg-blue-600:hover{background-color:#3182ce} +.rw-scaffold .hover\:bg-blue-700:hover{background-color:#2b6cb0} +.rw-scaffold .border-gray-300{border-color:#e2e8f0} +.rw-scaffold .border-red-300{border-color:#feb2b2} +.rw-scaffold .border-red-700{border-color:#c53030} +.rw-scaffold .focus\:border-gray-500:focus{border-color:#a0aec0} +.rw-scaffold .rounded-sm{border-radius:.125rem} +.rw-scaffold .rounded{border-radius:.25rem} +.rw-scaffold .rounded-lg{border-radius:.5rem} +.rw-scaffold .border{border-width:1px} +.rw-scaffold .border-t{border-top-width:1px} +.rw-scaffold .block{display:block} +.rw-scaffold .inline-block{display:inline-block} +.rw-scaffold .flex{display:flex} +.rw-scaffold .table{display:table} +.rw-scaffold .justify-between{justify-content:space-between} +.rw-scaffold .font-sans{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"} +.rw-scaffold .font-semibold{font-weight:600} +.rw-scaffold .leading-none{line-height:1} +.rw-scaffold .leading-loose{line-height:2} +.rw-scaffold .list-inside{list-style-position:inside} +.rw-scaffold .list-disc{list-style-type:disc} +.rw-scaffold .mx-2{margin-left:.5rem;margin-right:.5rem} +.rw-scaffold .my-4{margin-top:1rem;margin-bottom:1rem} +.rw-scaffold .mx-4{margin-left:1rem;margin-right:1rem} +.rw-scaffold .mt-0{margin-top:0} +.rw-scaffold .mt-1{margin-top:.25rem} +.rw-scaffold .ml-1{margin-left:.25rem} +.rw-scaffold .mt-2{margin-top:.5rem} +.rw-scaffold .ml-2{margin-left:.5rem} +.rw-scaffold .mt-4{margin-top:1rem} +.rw-scaffold .mb-4{margin-bottom:1rem} +.rw-scaffold .mt-6{margin-top:1.5rem} +.rw-scaffold .mt-8{margin-top:2rem} +.rw-scaffold .-mt-4{margin-top:-1rem} +.rw-scaffold .focus\:outline-none:focus{outline:0} +.rw-scaffold .overflow-hidden{overflow:hidden} +.rw-scaffold .overflow-x-scroll{overflow-x:scroll} +.rw-scaffold .p-2{padding:.5rem} +.rw-scaffold .p-3{padding:.75rem} +.rw-scaffold .p-4{padding:1rem} +.rw-scaffold .py-1{padding-top:.25rem;padding-bottom:.25rem} +.rw-scaffold .py-2{padding-top:.5rem;padding-bottom:.5rem} +.rw-scaffold .px-2{padding-left:.5rem;padding-right:.5rem} +.rw-scaffold .py-3{padding-top:.75rem;padding-bottom:.75rem} +.rw-scaffold .px-3{padding-left:.75rem;padding-right:.75rem} +.rw-scaffold .py-4{padding-top:1rem;padding-bottom:1rem} +.rw-scaffold .px-4{padding-left:1rem;padding-right:1rem} +.rw-scaffold .px-8{padding-left:2rem;padding-right:2rem} +.rw-scaffold .pr-4{padding-right:1rem} +.rw-scaffold .pb-4{padding-bottom:1rem} +.rw-scaffold .table-auto{table-layout:auto} +.rw-scaffold .text-left{text-align:left} +.rw-scaffold .text-center{text-align:center} +.rw-scaffold .text-right{text-align:right} +.rw-scaffold .text-white{color:#fff} +.rw-scaffold .text-gray-600{color:#718096} +.rw-scaffold .text-gray-700{color:#4a5568} +.rw-scaffold .text-gray-900{color:#1a202c} +.rw-scaffold .text-red-600{color:#e53e3e} +.rw-scaffold .text-red-700{color:#c53030} +.rw-scaffold .text-red-900{color:#742a2a} +.rw-scaffold .text-blue-500{color:#4299e1} +.rw-scaffold .text-blue-600{color:#3182ce} +.rw-scaffold .hover\:text-white:hover{color:#fff} +.rw-scaffold .hover\:text-gray-900:hover{color:#1a202c} +.rw-scaffold .hover\:text-blue-700:hover{color:#2b6cb0} +.rw-scaffold .text-xs{font-size:.75rem} +.rw-scaffold .text-sm{font-size:.875rem} +.rw-scaffold .text-xl{font-size:1.25rem} +.rw-scaffold .uppercase{text-transform:uppercase} +.rw-scaffold .underline{text-decoration:underline} +.rw-scaffold .hover\:underline:hover{text-decoration:underline} +.rw-scaffold .tracking-wide{letter-spacing:.025em} +.rw-scaffold .whitespace-no-wrap{white-space:nowrap} +.rw-scaffold .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.rw-scaffold .w-full{width:100%} +.rw-scaffold .box-border{box-sizing:border-box} +@media (min-width:768px) { + .rw-scaffold .md\:w-1\/5{width:20%} +} + +/* + Here are variants (:odd, :even) that aren't enabled in Tailwind by default. + If you decide to include Tailwind yourself and still want scaffolds to look + the same then you can tell it to build these variants by adding the following + to tailwind.config.js: + + module.exports = { + variants: { + backgroundColor: ['hover', 'focus', 'odd', 'even'] + } + } + + Tailwind variant customization docs: https://tailwindcss.com/docs/configuring-variants +*/ + +.rw-scaffold .odd\:bg-gray-100:nth-child(odd) {background-color: #f7fafc} +.rw-scaffold .even\:bg-white:nth-child(even) {background-color: #ffffff;} + +/* + Any custom styles that Tailwind doesn't provide. You can add these to your + own install of Tailwind by adding the following to tailwind.config.js: + + module.exports = { + theme: { + extend: { + minWidth: { + '3xl': '48rem' + } + } + } + } + + Tailwind theme extension docs: https://tailwindcss.com/docs/theme#extending-the-default-theme +*/ + +.rw-scaffold .min-w-3xl {min-width: 48rem} diff --git a/packages/cli/src/commands/generate/templates/scaffold/components/EditNameCell.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/components/EditNameCell.js.template rename to packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/components/Name.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/Name.js.template similarity index 98% rename from packages/cli/src/commands/generate/templates/scaffold/components/Name.js.template rename to packages/cli/src/commands/generate/scaffold/templates/components/Name.js.template index 7aac76a93ec3..84b6775adeea 100644 --- a/packages/cli/src/commands/generate/templates/scaffold/components/Name.js.template +++ b/packages/cli/src/commands/generate/scaffold/templates/components/Name.js.template @@ -34,8 +34,7 @@ const ${singularPascalName} = ({ ${singularCamelName} }) => { <% columns.forEach(column => { %> <%= column.name %> {${singularCamelName}.<%= column.name %>} - - <% }) %> + <% }) %>
diff --git a/packages/cli/src/commands/generate/templates/scaffold/components/NameCell.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/NameCell.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/components/NameCell.js.template rename to packages/cli/src/commands/generate/scaffold/templates/components/NameCell.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/components/NameForm.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/NameForm.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/components/NameForm.js.template rename to packages/cli/src/commands/generate/scaffold/templates/components/NameForm.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/components/Names.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/Names.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/components/Names.js.template rename to packages/cli/src/commands/generate/scaffold/templates/components/Names.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/components/NamesCell.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/NamesCell.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/components/NamesCell.js.template rename to packages/cli/src/commands/generate/scaffold/templates/components/NamesCell.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/components/NewName.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/components/NewName.js.template rename to packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/layouts/NamesLayout.js.template b/packages/cli/src/commands/generate/scaffold/templates/layouts/NamesLayout.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/layouts/NamesLayout.js.template rename to packages/cli/src/commands/generate/scaffold/templates/layouts/NamesLayout.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/pages/EditNamePage.js.template b/packages/cli/src/commands/generate/scaffold/templates/pages/EditNamePage.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/pages/EditNamePage.js.template rename to packages/cli/src/commands/generate/scaffold/templates/pages/EditNamePage.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/pages/NamePage.js.template b/packages/cli/src/commands/generate/scaffold/templates/pages/NamePage.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/pages/NamePage.js.template rename to packages/cli/src/commands/generate/scaffold/templates/pages/NamePage.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/pages/NamesPage.js.template b/packages/cli/src/commands/generate/scaffold/templates/pages/NamesPage.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/pages/NamesPage.js.template rename to packages/cli/src/commands/generate/scaffold/templates/pages/NamesPage.js.template diff --git a/packages/cli/src/commands/generate/templates/scaffold/pages/NewNamePage.js.template b/packages/cli/src/commands/generate/scaffold/templates/pages/NewNamePage.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/scaffold/pages/NewNamePage.js.template rename to packages/cli/src/commands/generate/scaffold/templates/pages/NewNamePage.js.template diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js new file mode 100644 index 000000000000..3f11d703b495 --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js @@ -0,0 +1,17 @@ +export const schema = gql` + type UserProfile { + id: Int! + username: String! + userId: Int! + user: User! + } + + type Query { + userProfiles: [UserProfile] + } + + input UserProfileInput { + username: String + userId: Int + } +` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js new file mode 100644 index 000000000000..c67569e9a5b8 --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js @@ -0,0 +1,24 @@ +export const schema = gql` + type UserProfile { + id: Int! + username: String! + userId: Int! + user: User! + } + + type Query { + userProfiles: [UserProfile] + userProfile(id: Int!): UserProfile + } + + input UserProfileInput { + username: String + userId: Int + } + + type Mutation { + createUserProfile(input: UserProfileInput!): UserProfile + updateUserProfile(id: Int!, input: UserProfileInput!): UserProfile + deleteUserProfile(id: Int!): UserProfile + } +` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/schema.prisma b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/schema.prisma new file mode 100644 index 000000000000..2ec3d87df3fa --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/schema.prisma @@ -0,0 +1,37 @@ +datasource hammerDatasource { + provider = "sqlite" + url = env("DB_HOST") +} + +generator client { + provider = "prisma-client-js" + binaryTargets = ["native", "rhel-openssl-1.0.x"] +} + +// Define your own models here and run yarn db:save to create +// migrations for them. + +model Post { + id Int @id @default(autoincrement()) + title String + slug String @unique + author String + body String + image String? + postedAt DateTime? +} + +model User { + id Int @id @default(autoincrement()) + name String? + email String @unique + isAdmin Boolean @default(false) + profiles UserProfile[] +} + +model UserProfile { + id Int @id @default(autoincrement()) + username String @unique + userId Int + user User @relation(fields: [userId], references: [id]) +} diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js new file mode 100644 index 000000000000..9f90ce2314cc --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js @@ -0,0 +1,19 @@ +export const schema = gql` + type User { + id: Int! + name: String + email: String! + isAdmin: Boolean! + profiles: UserProfile + } + + type Query { + users: [User] + } + + input UserInput { + name: String + email: String + isAdmin: Boolean + } +` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js new file mode 100644 index 000000000000..50321a35c35b --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js @@ -0,0 +1,31 @@ +export const schema = gql` + type Post { + id: Int! + title: String! + slug: String! + author: String! + body: String! + image: String + postedAt: DateTime + } + + type Query { + posts: [Post] + post(id: Int!): Post + } + + input PostInput { + title: String + slug: String + author: String + body: String + image: String + postedAt: DateTime + } + + type Mutation { + createPost(input: PostInput!): Post + updatePost(id: Int!, input: PostInput!): Post + deletePost(id: Int!): Post + } +` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js b/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js new file mode 100644 index 000000000000..1d7e3cb151f4 --- /dev/null +++ b/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js @@ -0,0 +1,80 @@ +global.__dirname = __dirname +import path from 'path' + +import { + loadFixture, + loadGeneratorFixture, + serviceFixturesPath, +} from 'src/lib/test' + +import * as sdl from '../sdl' + +afterEach(() => { + jest.clearAllMocks() +}) + +test('returns exactly 3 files', async () => { + const files = await sdl.files({ name: 'Post', crud: false }) + + expect(Object.keys(files).length).toEqual(3) +}) + +test('creates a service', async () => { + const files = await sdl.files({ name: 'User', crud: false }) + + expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( + loadFixture(path.join(serviceFixturesPath, 'singleWordService.js')) + ) +}) + +test('creates a service test', async () => { + const files = await sdl.files({ name: 'User', crud: false }) + + expect( + files['/path/to/project/api/src/services/users/users.test.js'] + ).toEqual( + loadFixture(path.join(serviceFixturesPath, 'singleWordService.test.js')) + ) +}) + +test('the generated service inherits crud setting', async () => { + const files = await sdl.files({ name: 'Post', crud: true }) + + expect( + files['/path/to/project/api/src/services/posts/posts.test.js'] + ).toEqual( + loadFixture(path.join(serviceFixturesPath, 'singleWordServiceCrud.test.js')) + ) +}) + +test('creates a single word sdl file', async () => { + const files = await sdl.files({ name: 'User', crud: false }) + + expect(files['/path/to/project/api/src/graphql/users.sdl.js']).toEqual( + loadGeneratorFixture('sdl', 'singleWordSdl.js') + ) +}) + +test('creates a multi word sdl file', async () => { + const files = await sdl.files({ name: 'UserProfile', crud: false }) + + expect(files['/path/to/project/api/src/graphql/userProfiles.sdl.js']).toEqual( + loadGeneratorFixture('sdl', 'multiWordSdl.js') + ) +}) + +test('creates a single word sdl file with CRUD actions', async () => { + const files = await sdl.files({ name: 'Post', crud: true }) + + expect(files['/path/to/project/api/src/graphql/posts.sdl.js']).toEqual( + loadGeneratorFixture('sdl', 'singleWordSdlCrud.js') + ) +}) + +test('creates a multi word sdl file with CRUD actions', async () => { + const files = await sdl.files({ name: 'UserProfile', crud: true }) + + expect(files['/path/to/project/api/src/graphql/userProfiles.sdl.js']).toEqual( + loadGeneratorFixture('sdl', 'multiWordSdlCrud.js') + ) +}) diff --git a/packages/cli/src/commands/generate/commands/sdl.js b/packages/cli/src/commands/generate/sdl/sdl.js similarity index 84% rename from packages/cli/src/commands/generate/commands/sdl.js rename to packages/cli/src/commands/generate/sdl/sdl.js index 4c4f022188e4..fc0ca1bb3139 100644 --- a/packages/cli/src/commands/generate/commands/sdl.js +++ b/packages/cli/src/commands/generate/sdl/sdl.js @@ -8,7 +8,7 @@ import pluralize from 'pluralize' import { generateTemplate, getSchema, getPaths, writeFilesTask } from 'src/lib' import c from 'src/lib/colors' -import { files as serviceFiles } from './service' +import { files as serviceFiles } from '../service/service' const IGNORE_FIELDS_FOR_INPUT = ['id', 'createdAt'] @@ -78,19 +78,25 @@ export const files = async ({ name, crud }) => { pascalcase(pluralize.singular(name)) ) - const template = generateTemplate(path.join('sdl', 'sdl.js.template'), { - name, - crud, - query, - input, - idType, - }) + const template = generateTemplate( + path.join('sdl', 'templates', 'sdl.js.template'), + { + name, + crud, + query, + input, + idType, + } + ) const outputPath = path.join( getPaths().api.graphql, `${camelcase(pluralize(name))}.sdl.js` ) - return { [outputPath]: template } + return { + [outputPath]: template, + ...(await serviceFiles({ name, crud })), + } } export const command = 'sdl ' @@ -101,7 +107,7 @@ export const builder = { force: { type: 'boolean', default: false }, } // TODO: Add --dry-run command -export const handler = async ({ model, crud, services, force }) => { +export const handler = async ({ model, crud, force }) => { const tasks = new Listr( [ { @@ -111,13 +117,6 @@ export const handler = async ({ model, crud, services, force }) => { return writeFilesTask(f, { overwriteExisting: force }) }, }, - services && { - title: 'Generating service files...', - task: async () => { - const f = await serviceFiles({ name: model, crud }) - return writeFilesTask(f, { overwriteExisting: force }) - }, - }, ].filter(Boolean), { collapse: false, exitOnError: true } ) diff --git a/packages/cli/src/commands/generate/templates/sdl/sdl.js.template b/packages/cli/src/commands/generate/sdl/templates/sdl.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/sdl/sdl.js.template rename to packages/cli/src/commands/generate/sdl/templates/sdl.js.template diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.js new file mode 100644 index 000000000000..2f54fb24b2c6 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.js @@ -0,0 +1,5 @@ +import { db } from 'src/lib/db' + +export const userProfiles = () => { + return db.userProfile.findMany() +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.test.js new file mode 100644 index 000000000000..0c1f26ab30ed --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordService.test.js @@ -0,0 +1,7 @@ +import { userProfiles } from './userProfiles' + +describe('userProfiles', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.js new file mode 100644 index 000000000000..8be74a61b2e7 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.js @@ -0,0 +1,30 @@ +import { db } from 'src/lib/db' + +export const userProfiles = () => { + return db.userProfile.findMany() +} + +export const userProfile = ({ id }) => { + return db.userProfile.findOne({ + where: { id }, + }) +} + +export const createUserProfile = ({ input }) => { + return db.userProfile.create({ + data: input, + }) +} + +export const updateUserProfile = ({ id, input }) => { + return db.userProfile.update({ + data: input, + where: { id }, + }) +} + +export const deleteUserProfile = ({ id }) => { + return db.userProfile.delete({ + where: { id }, + }) +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.test.js new file mode 100644 index 000000000000..0c1f26ab30ed --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWordServiceCrud.test.js @@ -0,0 +1,7 @@ +import { userProfiles } from './userProfiles' + +describe('userProfiles', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.js new file mode 100644 index 000000000000..b5bfed92100b --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.js @@ -0,0 +1,5 @@ +import { db } from 'src/lib/db' + +export const users = () => { + return db.user.findMany() +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.test.js new file mode 100644 index 000000000000..5a73412f0b9d --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordService.test.js @@ -0,0 +1,7 @@ +import { users } from './users' + +describe('users', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.js new file mode 100644 index 000000000000..be53d32bf06f --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.js @@ -0,0 +1,30 @@ +import { db } from 'src/lib/db' + +export const posts = () => { + return db.post.findMany() +} + +export const post = ({ id }) => { + return db.post.findOne({ + where: { id }, + }) +} + +export const createPost = ({ input }) => { + return db.post.create({ + data: input, + }) +} + +export const updatePost = ({ id, input }) => { + return db.post.update({ + data: input, + where: { id }, + }) +} + +export const deletePost = ({ id }) => { + return db.post.delete({ + where: { id }, + }) +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.test.js new file mode 100644 index 000000000000..b5ff8afbdfc3 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWordServiceCrud.test.js @@ -0,0 +1,7 @@ +import { posts } from './posts' + +describe('posts', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/generate/service/__tests__/service.test.js b/packages/cli/src/commands/generate/service/__tests__/service.test.js new file mode 100644 index 000000000000..5a3608281f54 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/service.test.js @@ -0,0 +1,74 @@ +global.__dirname = __dirname +import { loadGeneratorFixture } from 'src/lib/test' + +import * as service from '../service' + +test('returns exactly 2 files', async () => { + const files = await service.files({ name: 'User', crud: false }) + + expect(Object.keys(files).length).toEqual(2) +}) + +test('creates a single word service file', async () => { + const files = await service.files({ name: 'User', crud: false }) + + expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( + loadGeneratorFixture('service', 'singleWordService.js') + ) +}) + +test('creates a single word service test file', async () => { + const files = await service.files({ name: 'User', crud: false }) + + expect( + files['/path/to/project/api/src/services/users/users.test.js'] + ).toEqual(loadGeneratorFixture('service', 'singleWordService.test.js')) +}) + +test('creates a multi word service file', async () => { + const files = await service.files({ name: 'UserProfile', crud: false }) + + expect( + files['/path/to/project/api/src/services/userProfiles/userProfiles.js'] + ).toEqual(loadGeneratorFixture('service', 'multiWordService.js')) +}) + +test('creates a multi word service test file', async () => { + const files = await service.files({ name: 'UserProfile', crud: false }) + + expect( + files['/path/to/project/api/src/services/userProfiles/userProfiles.test.js'] + ).toEqual(loadGeneratorFixture('service', 'multiWordService.test.js')) +}) + +test('creates a single word service file with CRUD actions', async () => { + const files = await service.files({ name: 'Post', crud: true }) + + expect(files['/path/to/project/api/src/services/posts/posts.js']).toEqual( + loadGeneratorFixture('service', 'singleWordServiceCrud.js') + ) +}) + +test('creates a service test file with CRUD actions', async () => { + const files = await service.files({ name: 'Post', crud: true }) + + expect( + files['/path/to/project/api/src/services/posts/posts.test.js'] + ).toEqual(loadGeneratorFixture('service', 'singleWordServiceCrud.test.js')) +}) + +test('creates a multi word service file with CRUD actions', async () => { + const files = await service.files({ name: 'UserProfile', crud: true }) + + expect( + files['/path/to/project/api/src/services/userProfiles/userProfiles.js'] + ).toEqual(loadGeneratorFixture('service', 'multiWordServiceCrud.js')) +}) + +test('creates a multi word service test file with CRUD actions', async () => { + const files = await service.files({ name: 'UserProfile', crud: true }) + + expect( + files['/path/to/project/api/src/services/userProfiles/userProfiles.test.js'] + ).toEqual(loadGeneratorFixture('service', 'multiWordServiceCrud.test.js')) +}) diff --git a/packages/cli/src/commands/generate/commands/service.js b/packages/cli/src/commands/generate/service/service.js similarity index 89% rename from packages/cli/src/commands/generate/commands/service.js rename to packages/cli/src/commands/generate/service/service.js index 104f6533bf42..4706fba850b3 100644 --- a/packages/cli/src/commands/generate/commands/service.js +++ b/packages/cli/src/commands/generate/service/service.js @@ -12,7 +12,8 @@ export const files = async ({ name, ...rest }) => { name, componentName: componentName, apiPathSection: 'services', - templatePath: 'service/service.js.template', + generator: 'service', + templatePath: 'service.js.template', templateVars: { ...rest }, }) const testFile = templateForComponentFile({ @@ -20,7 +21,8 @@ export const files = async ({ name, ...rest }) => { componentName: componentName, extension: '.test.js', apiPathSection: 'services', - templatePath: 'service/test.js.template', + generator: 'service', + templatePath: 'test.js.template', templateVars: { ...rest }, }) diff --git a/packages/cli/src/commands/generate/templates/service/service.js.template b/packages/cli/src/commands/generate/service/templates/service.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/service/service.js.template rename to packages/cli/src/commands/generate/service/templates/service.js.template diff --git a/packages/cli/src/commands/generate/templates/service/test.js.template b/packages/cli/src/commands/generate/service/templates/test.js.template similarity index 100% rename from packages/cli/src/commands/generate/templates/service/test.js.template rename to packages/cli/src/commands/generate/service/templates/test.js.template diff --git a/packages/cli/src/lib/__mocks__/index.js b/packages/cli/src/lib/__mocks__/index.js deleted file mode 100644 index 1d12f8ffd4ba..000000000000 --- a/packages/cli/src/lib/__mocks__/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export const runCommandTask = jest.fn((commands) => { - return commands.map(({ cmd, args }) => `${cmd} ${args.join(' ')}`) -}) - -export const getPaths = () => ({ api: {}, web: {} }) diff --git a/packages/cli/src/lib/__tests__/fixtures/code.js b/packages/cli/src/lib/__tests__/fixtures/code.js new file mode 100644 index 000000000000..7631e7768c66 --- /dev/null +++ b/packages/cli/src/lib/__tests__/fixtures/code.js @@ -0,0 +1,2 @@ +const line1 = "The quick brown ${pluralCamelName} jumps over the lazy ${foo}."; +const line2 = 'Sphinx of black quartz, judge my vow.' diff --git a/packages/cli/src/lib/__tests__/fixtures/postSchema.json.fixture b/packages/cli/src/lib/__tests__/fixtures/postSchema.json.fixture new file mode 100644 index 000000000000..ec8a80f798eb --- /dev/null +++ b/packages/cli/src/lib/__tests__/fixtures/postSchema.json.fixture @@ -0,0 +1,99 @@ +{ + "dbName": null, + "documentation": "Define your own models here and run yarn db:save to create migrations for them.", + "fields": [ + { + "dbNames": [], + "default": { + "args": [], + "name": "autoincrement" + }, + "isGenerated": false, + "isId": true, + "isList": false, + "isRequired": true, + "isUnique": false, + "isUpdatedAt": false, + "kind": "scalar", + "name": "id", + "type": "Int" + }, + { + "dbNames": [], + "isGenerated": false, + "isId": false, + "isList": false, + "isRequired": true, + "isUnique": false, + "isUpdatedAt": false, + "kind": "scalar", + "name": "title", + "type": "String" + }, + { + "dbNames": [], + "isGenerated": false, + "isId": false, + "isList": false, + "isRequired": true, + "isUnique": true, + "isUpdatedAt": false, + "kind": "scalar", + "name": "slug", + "type": "String" + }, + { + "dbNames": [], + "isGenerated": false, + "isId": false, + "isList": false, + "isRequired": true, + "isUnique": false, + "isUpdatedAt": false, + "kind": "scalar", + "name": "author", + "type": "String" + }, + { + "dbNames": [], + "isGenerated": false, + "isId": false, + "isList": false, + "isRequired": true, + "isUnique": false, + "isUpdatedAt": false, + "kind": "scalar", + "name": "body", + "type": "String" + }, + { + "dbNames": [], + "isGenerated": false, + "isId": false, + "isList": false, + "isRequired": false, + "isUnique": false, + "isUpdatedAt": false, + "kind": "scalar", + "name": "image", + "type": "String" + }, + { + "dbNames": [], + "isGenerated": false, + "isId": false, + "isList": false, + "isRequired": false, + "isUnique": false, + "isUpdatedAt": false, + "kind": "scalar", + "name": "postedAt", + "type": "DateTime" + } + ], + "idFields": [], + "isEmbedded": false, + "isGenerated": false, + "name": "Post", + "uniqueFields": [] +} diff --git a/packages/cli/src/lib/__tests__/fixtures/prettier.config.js b/packages/cli/src/lib/__tests__/fixtures/prettier.config.js new file mode 100644 index 000000000000..c37f9c534bb7 --- /dev/null +++ b/packages/cli/src/lib/__tests__/fixtures/prettier.config.js @@ -0,0 +1,8 @@ +module.exports = { + trailingComma: 'es5', + bracketSpacing: true, + tabWidth: 2, + semi: false, + singleQuote: true, + arrowParens: 'always', +} diff --git a/packages/cli/src/lib/__tests__/fixtures/schema.prisma b/packages/cli/src/lib/__tests__/fixtures/schema.prisma new file mode 100644 index 000000000000..2ec3d87df3fa --- /dev/null +++ b/packages/cli/src/lib/__tests__/fixtures/schema.prisma @@ -0,0 +1,37 @@ +datasource hammerDatasource { + provider = "sqlite" + url = env("DB_HOST") +} + +generator client { + provider = "prisma-client-js" + binaryTargets = ["native", "rhel-openssl-1.0.x"] +} + +// Define your own models here and run yarn db:save to create +// migrations for them. + +model Post { + id Int @id @default(autoincrement()) + title String + slug String @unique + author String + body String + image String? + postedAt DateTime? +} + +model User { + id Int @id @default(autoincrement()) + name String? + email String @unique + isAdmin Boolean @default(false) + profiles UserProfile[] +} + +model UserProfile { + id Int @id @default(autoincrement()) + username String @unique + userId Int + user User @relation(fields: [userId], references: [id]) +} diff --git a/packages/cli/src/lib/__tests__/fixtures/text.txt b/packages/cli/src/lib/__tests__/fixtures/text.txt new file mode 100644 index 000000000000..23d4b6397813 --- /dev/null +++ b/packages/cli/src/lib/__tests__/fixtures/text.txt @@ -0,0 +1,2 @@ +Lorem ipsum dolar sit ${singularCamelName} +Hello, ${noun}! diff --git a/packages/cli/src/lib/__tests__/index.test.js b/packages/cli/src/lib/__tests__/index.test.js new file mode 100644 index 000000000000..69b5177f7444 --- /dev/null +++ b/packages/cli/src/lib/__tests__/index.test.js @@ -0,0 +1,103 @@ +global.__dirname = __dirname +jest.mock('@redwoodjs/internal', () => { + const path = require('path') + return { + ...require.requireActual('@redwoodjs/internal'), + getPaths: () => { + const BASE_PATH = path.join(global.__dirname, 'fixtures') + return { + base: BASE_PATH, + api: { + db: BASE_PATH, // this folder + }, + } + }, + } +}) + +import path from 'path' + +import * as index from '../index' + +test('getSchema returns a parsed schema.prisma', async () => { + let schema = await index.getSchema('Post') + expect(schema.fields[0].name).toEqual('id') + expect(schema.fields[1].name).toEqual('title') + expect(schema.fields[2].name).toEqual('slug') + + // can get a different model + schema = await index.getSchema('User') + expect(schema.fields[0].name).toEqual('id') + expect(schema.fields[1].name).toEqual('name') + expect(schema.fields[2].name).toEqual('email') +}) + +test('getSchema throws an error if model name not found', async () => { + let error + + try { + await index.getSchema('Foo') + } catch (e) { + error = e + } + + expect(error).toEqual(new Error(error.message)) +}) + +test('nameVariants returns a single word cased variables', () => { + const names = ['Home', 'home'] + + names.forEach((name) => { + const vars = index.nameVariants(name) + + expect(vars.pascalName).toEqual('Home') + expect(vars.camelName).toEqual('home') + expect(vars.singularPascalName).toEqual('Home') + expect(vars.pluralPascalName).toEqual('Homes') + expect(vars.singularCamelName).toEqual('home') + expect(vars.pluralCamelName).toEqual('homes') + expect(vars.singularParamName).toEqual('home') + expect(vars.pluralParamName).toEqual('homes') + }) +}) + +test('nameVariants returns a multi word cased variables', () => { + const names = ['FooBar', 'fooBar', 'foo_bar', 'foo-bar'] + + names.forEach((name) => { + const vars = index.nameVariants(name) + + expect(vars.pascalName).toEqual('FooBar') + expect(vars.camelName).toEqual('fooBar') + expect(vars.singularPascalName).toEqual('FooBar') + expect(vars.pluralPascalName).toEqual('FooBars') + expect(vars.singularCamelName).toEqual('fooBar') + expect(vars.pluralCamelName).toEqual('fooBars') + expect(vars.singularParamName).toEqual('foo-bar') + expect(vars.pluralParamName).toEqual('foo-bars') + }) +}) + +test('generateTemplate returns a lodash-templated string', () => { + const output = index.generateTemplate(path.join('fixtures', 'text.txt'), { + root: __dirname, + name: 'amet', + noun: 'world', + }) + + expect(output).toEqual(`Lorem ipsum dolar sit amet\nHello, world!\n`) +}) + +// Be careful when editing the code.js fixture as the prettifier.config.js will cause it to get +// prettified and then it already match the expected output, with no changes +test('generateTemplate returns prettified JS code', () => { + const output = index.generateTemplate(path.join('fixtures', 'code.js'), { + root: __dirname, + name: 'fox', + foo: 'dog', + }) + + expect(output).toEqual( + `const line1 = 'The quick brown foxes jumps over the lazy dog.'\nconst line2 = 'Sphinx of black quartz, judge my vow.'\n` + ) +}) diff --git a/packages/cli/src/lib/index.js b/packages/cli/src/lib/index.js index 4edcd068ec85..daf1d4cd849d 100644 --- a/packages/cli/src/lib/index.js +++ b/packages/cli/src/lib/index.js @@ -13,7 +13,7 @@ import Listr from 'listr' import VerboseRenderer from 'listr-verbose-renderer' import { format } from 'prettier' -import c from 'src/lib/colors' +import c from './colors' export const asyncForEach = async (array, callback) => { for (let index = 0; index < array.length; index++) { @@ -40,7 +40,9 @@ export const getSchema = async (name) => { if (model) { return model } else { - throw `No schema definition found for \`${name}\` in schema.prisma file` + throw new Error( + `No schema definition found for \`${name}\` in schema.prisma file` + ) } } @@ -59,8 +61,8 @@ export const getSchema = async (name) => { * singularParamName: foo-bar * pluralParamName: foo-bars */ -const nameVariants = (name) => { - const normalizedName = pascalcase(pluralize.singular(name)) +export const nameVariants = (name) => { + const normalizedName = pascalcase(paramCase(pluralize.singular(name))) return { pascalName: pascalcase(name), @@ -74,13 +76,10 @@ const nameVariants = (name) => { } } -export const templateRoot = path.resolve( - __dirname, - '../commands/generate/templates' -) +export const templateRoot = path.resolve(__dirname, '../commands/generate') -export const generateTemplate = (templateFilename, { name, ...rest }) => { - const templatePath = path.join(templateRoot, templateFilename) +export const generateTemplate = (templateFilename, { name, root, ...rest }) => { + const templatePath = path.join(root || templateRoot, templateFilename) const template = lodash.template(readFile(templatePath).toString()) const renderedTemplate = template({ @@ -93,8 +92,8 @@ export const generateTemplate = (templateFilename, { name, ...rest }) => { // we're using. // https://prettier.io/docs/en/options.html#parser const parser = { - css: 'css', - js: 'babel', + '.css': 'css', + '.js': 'babel', }[path.extname(templateFilename)] if (typeof parser === 'undefined') { @@ -134,7 +133,7 @@ export const getPaths = () => { try { return getRedwoodPaths() } catch (e) { - console.log(c.error(e.message)) + console.error(c.error(e.message)) process.exit(0) } } diff --git a/packages/cli/src/lib/test.js b/packages/cli/src/lib/test.js new file mode 100644 index 000000000000..e2fb5387a399 --- /dev/null +++ b/packages/cli/src/lib/test.js @@ -0,0 +1,84 @@ +// Include at the top of your tests. Automatically mocks out the file system +// +// import { loadComponentFixture } from 'src/lib/test' +// +// test('true is true', () => { +// expect('some output').toEqual(loadComponentFixture('component', 'filename.js')) +// }) + +import fs from 'fs' +import path from 'path' + +jest.mock('@redwoodjs/internal', () => { + const path = require('path') + return { + ...require.requireActual('@redwoodjs/internal'), + getPaths: () => { + const BASE_PATH = '/path/to/project' + return { + base: BASE_PATH, + api: { + db: path.join(global.__dirname, 'fixtures'), // this folder + src: path.join(BASE_PATH, './api/src'), + services: path.join(BASE_PATH, './api/src/services'), + graphql: path.join(BASE_PATH, './api/src/graphql'), + }, + web: { + src: path.join(BASE_PATH, './web/src'), + routes: path.join(BASE_PATH, 'web/src/Routes.js'), + components: path.join(BASE_PATH, '/web/src/components'), + layouts: path.join(BASE_PATH, '/web/src/layouts'), + pages: path.join(BASE_PATH, '/web/src/pages'), + }, + } + }, + } +}) + +export const generatorsRootPath = path.join( + __dirname, + '..', + 'commands', + 'generate' +) + +export const sdlFixturesPath = path.join( + generatorsRootPath, + 'sdl', + '__tests__', + 'fixtures' +) + +export const serviceFixturesPath = path.join( + generatorsRootPath, + 'service', + '__tests__', + 'fixtures' +) + +// Loads the fixture for a generator by assuming a lot of the path structure automatically: +// +// loadGeneratorFixture('scaffold', 'NamePage.js') +// +// will return the contents of: +// +// cli/src/commands/generate/scaffold/test/fixtures/NamePage.js.fixture +export const loadGeneratorFixture = (generator, name) => { + return loadFixture( + path.join( + __dirname, + '..', + 'commands', + 'generate', + generator, + '__tests__', + 'fixtures', + name + ) + ) +} + +// Returns the contents of a text file suffixed with ".fixture" +export const loadFixture = (filepath) => { + return fs.readFileSync(filepath).toString() +} diff --git a/yarn.lock b/yarn.lock index 73af449a46aa..faf22faee529 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11669,7 +11669,7 @@ rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: dependencies: glob "^7.1.3" -rimraf@^3.0.0: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== From cd62c8931d5c340946b5cb60ba6e046f504dd3a2 Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Fri, 10 Apr 2020 19:47:56 +0200 Subject: [PATCH 16/55] Add tests for router parseSearch. --- packages/router/src/__tests__/util.test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/router/src/__tests__/util.test.js b/packages/router/src/__tests__/util.test.js index 43e0023442a1..e2e8413a3212 100644 --- a/packages/router/src/__tests__/util.test.js +++ b/packages/router/src/__tests__/util.test.js @@ -1,4 +1,4 @@ -import { matchPath } from '../util' +import { matchPath, parseSearch } from '../util' describe('matchPath', () => { it('matches paths correctly', () => { @@ -62,3 +62,15 @@ describe('matchPath', () => { }) }) }) + +describe('parseSearch', () => { + it('deals silently with an empty search string', () => { + expect(parseSearch('')).toEqual({}) + }) + + it('correctly parses a search string', () => { + expect( + parseSearch('?search=all+dogs+go+to+heaven&category=movies') + ).toEqual({ category: 'movies', search: 'all dogs go to heaven' }) + }) +}) From 3a18e6b2cbc3e2f9e8187b91aca4cf22fc53f0e2 Mon Sep 17 00:00:00 2001 From: David S Price Date: Fri, 10 Apr 2020 13:29:58 -0700 Subject: [PATCH 17/55] change prisma2 to prisma --- .../cli/src/commands/dbCommands/__tests__/dbCommands.test.js | 2 +- packages/cli/src/commands/dbCommands/introspect.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js b/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js index 488b21c5298d..fba71c7951df 100644 --- a/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js +++ b/packages/cli/src/commands/dbCommands/__tests__/dbCommands.test.js @@ -58,7 +58,7 @@ describe('db commands', () => { await introspect.handler({}) expect(runCommandTask.mock.results[5].value).toEqual([ - 'yarn prisma2 introspect', + 'yarn prisma introspect', ]) await seed.handler({}) diff --git a/packages/cli/src/commands/dbCommands/introspect.js b/packages/cli/src/commands/dbCommands/introspect.js index dd506d49d8b0..078121068139 100644 --- a/packages/cli/src/commands/dbCommands/introspect.js +++ b/packages/cli/src/commands/dbCommands/introspect.js @@ -12,7 +12,7 @@ export const handler = async ({ verbose = true }) => { [ { title: 'Introspecting your database...', - cmd: 'yarn prisma2', + cmd: 'yarn prisma', args: ['introspect'], opts: { cwd: getPaths().api.db }, }, From 4d392e8d1a3774a51213c29690a67ccc6c69582a Mon Sep 17 00:00:00 2001 From: Rob Cameron Date: Fri, 10 Apr 2020 16:41:01 -0700 Subject: [PATCH 18/55] Support table relationships in service generators (#412) * Remove global mocks, localize to test. * Remove more global mocks. * Make dbCommand tests work again. * Write snapshot tests for generators. * Add generate tests and snaps. * Play around with snapshot testing. * Update snapshots. * Complete reorg of generator files and templates, adds text and fixture directories Cell test working * Component, layout, page and service tests * Adds tests for generator helpers.js * More tests * Comment out some variables that aren't being used for now * Adds multi-word cell tests * Variable name change * Adds multi-word component tests * Component test fix * Adds multi-word layout tests * Adds multi-word page tests * Creates multi-word and CRUD sdl tests * Adds multi word tests for service * Adds scaffold tests * Count actual number of files returned * Rename test directories to __tests__ * Adds tests for src/lib/index * Ignore any files named "test" inside a fixtures directory * Adds generateTemplate tests * Ignore fixture files when linting * fix babel build ignore error using rimraf (rm -rf) Babel flag --no-copy-ignored has a bug and does not respect our ignored config. For a short term fix, I'm using rimraf package (cross-platform support) to clean up __tests__ folders that shouldn't be in dist/ * Converts SDL generator to use same sub-generator technique as scaffold generator * Updates README with new generator file structure * Removes .fixture extensions * Remove unused argument * Adds relations output to service generator * Update SDL tests for service relation creation * Gets scaffold to pass along relations, refactor relation lookup * Reorganize services fixtures * In scaffold cells, don't query for related objects, just foreign keys * Adds helper for extracting foreign key names that are type Int * Automatically cast foreign keys to Int in scaffold generated files Co-authored-by: Peter Pistorius Co-authored-by: Rob Cameron Co-authored-by: David S Price --- .../generate/__tests__/helpers.test.js | 220 ++++++++++++++++++ packages/cli/src/commands/generate/helpers.js | 19 ++ .../fixtures/components/foreignKeys/edit.js | 47 ++++ .../fixtures/components/foreignKeys/new.js | 38 +++ .../scaffold/__tests__/scaffold.test.js | 110 +++++++-- .../commands/generate/scaffold/scaffold.js | 13 +- .../components/EditNameCell.js.template | 6 +- .../templates/components/NewName.js.template | 6 +- .../generate/sdl/__tests__/sdl.test.js | 39 +--- packages/cli/src/commands/generate/sdl/sdl.js | 6 +- .../service/__tests__/fixtures/multiWord.js | 5 + .../__tests__/fixtures/multiWord.test.js | 7 + .../__tests__/fixtures/multiWord_crud.js | 30 +++ .../__tests__/fixtures/multiWord_crud.test.js | 7 + .../service/__tests__/fixtures/singleWord.js | 5 + .../__tests__/fixtures/singleWord.test.js | 7 + .../fixtures/singleWord_belongsTo.js | 9 + .../__tests__/fixtures/singleWord_crud.js | 30 +++ .../fixtures/singleWord_crud.test.js | 7 + .../__tests__/fixtures/singleWord_hasMany.js | 9 + .../__tests__/fixtures/singleWord_multiple.js | 10 + .../service/__tests__/service.test.js | 106 +++++++-- .../src/commands/generate/service/service.js | 6 +- .../service/templates/service.js.template | 4 + packages/cli/src/lib/test.js | 14 -- 25 files changed, 664 insertions(+), 96 deletions(-) create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js create mode 100644 packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.js create mode 100644 packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.js diff --git a/packages/cli/src/commands/generate/__tests__/helpers.test.js b/packages/cli/src/commands/generate/__tests__/helpers.test.js index 13cd903c6b20..01f347117ff7 100644 --- a/packages/cli/src/commands/generate/__tests__/helpers.test.js +++ b/packages/cli/src/commands/generate/__tests__/helpers.test.js @@ -117,3 +117,223 @@ test('pathName creates path based on name if path is null', () => { expect(helpers.pathName(null, name)).toEqual('/foo-bar') }) }) + +test('relationsForModel returns related field names from a belongs-to relationship', () => { + const model = { + name: 'UserProfile', + isEmbedded: false, + dbName: null, + fields: [ + { + name: 'id', + kind: 'scalar', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: true, + type: 'Int', + default: [Object], + isGenerated: false, + isUpdatedAt: false, + }, + { + name: 'userId', + kind: 'scalar', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: false, + type: 'Int', + isGenerated: false, + isUpdatedAt: false, + }, + { + name: 'user', + kind: 'object', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: false, + type: 'User', + relationName: 'UserToUserProfile', + relationToFields: [Array], + relationOnDelete: 'NONE', + isGenerated: false, + isUpdatedAt: false, + }, + ], + isGenerated: false, + idFields: [], + uniqueFields: [], + } + + expect(helpers.relationsForModel(model)).toEqual(['user']) +}) + +test('relationsForModel returns related field names from a has-many relationship', () => { + const model = { + name: 'User', + isEmbedded: false, + dbName: null, + fields: [ + { + name: 'id', + kind: 'scalar', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: true, + type: 'Int', + default: [Object], + isGenerated: false, + isUpdatedAt: false, + }, + { + name: 'name', + kind: 'scalar', + dbNames: [], + isList: false, + isRequired: false, + isUnique: false, + isId: false, + type: 'String', + isGenerated: false, + isUpdatedAt: false, + }, + { + name: 'profiles', + kind: 'object', + dbNames: [], + isList: true, + isRequired: false, + isUnique: false, + isId: false, + type: 'UserProfile', + relationName: 'UserToUserProfile', + relationToFields: [], + relationOnDelete: 'NONE', + isGenerated: false, + isUpdatedAt: false, + }, + ], + isGenerated: false, + idFields: [], + uniqueFields: [], + } + + expect(helpers.relationsForModel(model)).toEqual(['userProfiles']) +}) + +test('intForeignKeysForModel returns names of foreign keys that are Int datatypes', () => { + const model = { + name: 'UserProfile', + isEmbedded: false, + dbName: null, + fields: [ + { + name: 'id', + kind: 'scalar', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: true, + type: 'Int', + default: [Object], + isGenerated: false, + isUpdatedAt: false, + }, + { + name: 'userId', + kind: 'scalar', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: false, + type: 'Int', + isGenerated: false, + isUpdatedAt: false, + }, + { + name: 'user', + kind: 'object', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: false, + type: 'User', + relationName: 'UserToUserProfile', + relationToFields: [Array], + relationOnDelete: 'NONE', + isGenerated: false, + isUpdatedAt: false, + }, + ], + isGenerated: false, + idFields: [], + uniqueFields: [], + } + + expect(helpers.intForeignKeysForModel(model)).toEqual(['userId']) +}) + +test('intForeignKeysForModel does not include foreign keys of other datatypes', () => { + const model = { + name: 'UserProfile', + isEmbedded: false, + dbName: null, + fields: [ + { + name: 'id', + kind: 'scalar', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: true, + type: 'Int', + default: [Object], + isGenerated: false, + isUpdatedAt: false, + }, + { + name: 'userId', + kind: 'scalar', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: false, + type: 'String', + isGenerated: false, + isUpdatedAt: false, + }, + { + name: 'user', + kind: 'object', + dbNames: [], + isList: false, + isRequired: true, + isUnique: false, + isId: false, + type: 'User', + relationName: 'UserToUserProfile', + relationToFields: [Array], + relationOnDelete: 'NONE', + isGenerated: false, + isUpdatedAt: false, + }, + ], + isGenerated: false, + idFields: [], + uniqueFields: [], + } + + expect(helpers.intForeignKeysForModel(model)).toEqual([]) +}) diff --git a/packages/cli/src/commands/generate/helpers.js b/packages/cli/src/commands/generate/helpers.js index eab795921383..40d1a553c314 100644 --- a/packages/cli/src/commands/generate/helpers.js +++ b/packages/cli/src/commands/generate/helpers.js @@ -1,5 +1,7 @@ import path from 'path' +import camelcase from 'camelcase' +import pluralize from 'pluralize' import Listr from 'listr' import pascalcase from 'pascalcase' import { paramCase } from 'param-case' @@ -88,3 +90,20 @@ export const createYargsForComponentGeneration = ({ }, } } + +// Returns all relations to other models +export const relationsForModel = (model) => { + return model.fields + .filter((f) => f.relationName) + .map((field) => { + const relationName = camelcase(field.type) + return field.isList ? pluralize(relationName) : relationName + }) +} + +// Returns only relations that are of datatype Int +export const intForeignKeysForModel = (model) => { + return model.fields + .filter((f) => f.name.match(/Id$/) && f.type === 'Int') + .map((f) => f.name) +} diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js new file mode 100644 index 000000000000..d20e80f660fb --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js @@ -0,0 +1,47 @@ +import { useMutation } from '@redwoodjs/web' +import { navigate, routes } from '@redwoodjs/router' +import UserProfileForm from 'src/components/UserProfileForm' + +export const QUERY = gql` + query FIND_POST_BY_ID($id: Int!) { + userProfile: userProfile(id: $id) { + id + username + userId + } + } +` +const UPDATE_POST_MUTATION = gql` + mutation UpdateUserProfileMutation($id: Int!, $input: UserProfileInput!) { + updateUserProfile(id: $id, input: $input) { + id + } + } +` + +export const Loading = () =>
Loading...
+ +export const Success = ({ userProfile }) => { + const [updateUserProfile, { loading, error }] = useMutation(UPDATE_POST_MUTATION, { + onCompleted: () => { + navigate(routes.userProfiles()) + }, + }) + + const onSave = (input, id) => { + const castInput = input + castInput = Object.assign(castInput, parseInt(input.userId)) + updateUserProfile({ variables: { id, castInput } }) + } + + return ( +
+
+

Edit UserProfile {userProfile.id}

+
+
+ +
+
+ ) +} diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js new file mode 100644 index 000000000000..72f982b10096 --- /dev/null +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js @@ -0,0 +1,38 @@ +import { useMutation } from '@redwoodjs/web' +import { navigate, routes } from '@redwoodjs/router' +import UserProfileForm from 'src/components/UserProfileForm' + +const CREATE_POST_MUTATION = gql` + mutation CreateUserProfileMutation($input: UserProfileInput!) { + createUserProfile(input: $input) { + id + } + } +` + +const NewUserProfile = () => { + const [createUserProfile, { loading, error }] = useMutation(CREATE_POST_MUTATION, { + onCompleted: () => { + navigate(routes.userProfiles()) + }, + }) + + const onSave = (input) => { + const castInput = input + castInput = Object.assign(castInput, parseInt(input.userId)) + createUserProfile({ variables: { castInput } }) + } + + return ( +
+
+

New UserProfile

+
+
+ +
+
+ ) +} + +export default NewUserProfile diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js b/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js index bc36bf5b41ec..66a48a0f5ba7 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js @@ -1,12 +1,7 @@ global.__dirname = __dirname import path from 'path' -import { - loadFixture, - loadGeneratorFixture, - sdlFixturesPath, - serviceFixturesPath, -} from 'src/lib/test' +import { loadGeneratorFixture } from 'src/lib/test' import * as scaffold from '../scaffold' @@ -20,35 +15,33 @@ test('returns exactly 16 files', () => { expect(Object.keys(files).length).toEqual(16) }) -// styles - -test('creates a stylesheet', () => { - expect(files['/path/to/project/web/src/scaffold.css']).toEqual( - loadGeneratorFixture('scaffold', path.join('assets', 'scaffold.css')) - ) -}) - // SDL -test('creates a graphql sdl', () => { - expect(files['/path/to/project/api/src/graphql/posts.sdl.js']).toEqual( - loadFixture(path.join(sdlFixturesPath, 'singleWordSdlCrud.js')) - ) +test('creates an sdl', () => { + expect(files).toHaveProperty([ + '/path/to/project/api/src/graphql/posts.sdl.js', + ]) }) // Service test('creates a service', () => { - expect(files['/path/to/project/api/src/services/posts/posts.js']).toEqual( - loadFixture(path.join(serviceFixturesPath, 'singleWordServiceCrud.js')) - ) + expect(files).toHaveProperty([ + '/path/to/project/api/src/services/posts/posts.js', + ]) }) test('creates a service test', () => { - expect( - files['/path/to/project/api/src/services/posts/posts.test.js'] - ).toEqual( - loadFixture(path.join(serviceFixturesPath, 'singleWordServiceCrud.test.js')) + expect(files).toHaveProperty([ + '/path/to/project/api/src/services/posts/posts.test.js', + ]) +}) + +// styles + +test('creates a stylesheet', () => { + expect(files['/path/to/project/web/src/scaffold.css']).toEqual( + loadGeneratorFixture('scaffold', path.join('assets', 'scaffold.css')) ) }) @@ -161,3 +154,70 @@ test('creates a multi-word name routes', async () => { '', ]) }) + +// GraphQL queries + +test('the GraphQL in the index query does not contain object types', async () => { + const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) + const cell = + userProfileFiles[ + '/path/to/project/web/src/components/UserProfilesCell/UserProfilesCell.js' + ] + const query = cell.match(/(userProfiles.*?\})/s)[1] + + expect(query).not.toMatch(/^\s+user$/m) +}) + +test('the GraphQL in the show query does not contain object types', async () => { + const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) + const cell = + userProfileFiles[ + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + ] + const query = cell.match(/(userProfile.*?\})/s)[1] + + expect(query).not.toMatch(/^\s+user$/m) +}) + +test('the GraphQL in the edit query does not contain object types', async () => { + const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) + const cell = + userProfileFiles[ + '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + ] + const query = cell.match(/(userProfile.*?\})/s)[1] + + expect(query).not.toMatch(/^\s+user$/m) +}) + +// Foreign key casting + +test('creates a new component with int foreign keys converted in onSave', async () => { + const foreignKeyFiles = await scaffold.files({ model: 'UserProfile' }) + + expect( + foreignKeyFiles[ + '/path/to/project/web/src/components/NewUserProfile/NewUserProfile.js' + ] + ).toEqual( + loadGeneratorFixture( + 'scaffold', + path.join('components', 'foreignKeys', 'new.js') + ) + ) +}) + +test('creates an edit component with int foreign keys converted in onSave', async () => { + const foreignKeyFiles = await scaffold.files({ model: 'UserProfile' }) + + expect( + foreignKeyFiles[ + '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + ] + ).toEqual( + loadGeneratorFixture( + 'scaffold', + path.join('components', 'foreignKeys', 'edit.js') + ) + ) +}) diff --git a/packages/cli/src/commands/generate/scaffold/scaffold.js b/packages/cli/src/commands/generate/scaffold/scaffold.js index e32a1dc0a052..543b7a36073d 100644 --- a/packages/cli/src/commands/generate/scaffold/scaffold.js +++ b/packages/cli/src/commands/generate/scaffold/scaffold.js @@ -20,6 +20,7 @@ import { } from 'src/lib' import c from 'src/lib/colors' +import { relationsForModel, intForeignKeysForModel } from '../helpers' import { files as sdlFiles } from '../sdl/sdl' import { files as serviceFiles } from '../service/service' @@ -45,9 +46,15 @@ const getIdType = (model) => { } export const files = async ({ model: name }) => { + const model = await getSchema(pascalcase(pluralize.singular(name))) + return { ...(await sdlFiles({ name, crud: true })), - ...(await serviceFiles({ name, crud: true })), + ...(await serviceFiles({ + name, + crud: true, + relations: relationsForModel(model), + })), ...assetFiles(name), ...layoutFiles(name), ...pageFiles(name), @@ -139,7 +146,8 @@ const componentFiles = async (name) => { const singularName = pascalcase(pluralize.singular(name)) const model = await getSchema(singularName) const idType = getIdType(model) - const columns = model.fields + const columns = model.fields.filter((field) => field.kind !== 'object') + const intForeignKeys = intForeignKeysForModel(model) let fileList = {} const editableColumns = columns.filter((column) => { return NON_EDITABLE_COLUMNS.indexOf(column.name) === -1 @@ -163,6 +171,7 @@ const componentFiles = async (name) => { columns, editableColumns, idType, + intForeignKeys, } ) fileList[outputPath] = template diff --git a/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template index 4ed71a59027b..4c1bb79f4d4a 100644 --- a/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template +++ b/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template @@ -26,8 +26,10 @@ export const Success = ({ ${singularCamelName} }) => { }, }) - const onSave = (input, id) => { - update${singularPascalName}({ variables: { id, input } }) + const onSave = (input, id) => {<% if (intForeignKeys.length) { %> + const castInput = input + <% intForeignKeys.forEach(key => { %>castInput = Object.assign(castInput, parseInt(input.${key}))<% }) %><% } %> + update${singularPascalName}({ variables: { id, <% if (intForeignKeys.length) { %>castInput<% } else { %>input<% } %> } }) } return ( diff --git a/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template index 7430fbc0b84d..ffe4d0f7a93b 100644 --- a/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template +++ b/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template @@ -17,8 +17,10 @@ const New${singularPascalName} = () => { }, }) - const onSave = (input) => { - create${singularPascalName}({ variables: { input } }) + const onSave = (input) => {<% if (intForeignKeys.length) { %> + const castInput = input + <% intForeignKeys.forEach(key => { %>castInput = Object.assign(castInput, parseInt(input.${key}))<% }) %><% } %> + create${singularPascalName}({ variables: { <% if (intForeignKeys.length) { %>castInput<% } else { %>input<% } %> } }) } return ( diff --git a/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js b/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js index 1d7e3cb151f4..4ad2fa15f1c3 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js @@ -1,11 +1,6 @@ global.__dirname = __dirname -import path from 'path' -import { - loadFixture, - loadGeneratorFixture, - serviceFixturesPath, -} from 'src/lib/test' +import { loadGeneratorFixture } from 'src/lib/test' import * as sdl from '../sdl' @@ -19,32 +14,18 @@ test('returns exactly 3 files', async () => { expect(Object.keys(files).length).toEqual(3) }) +// in this case we'll trust that a service and test are actually created +// with the correct filename, but the contents of that file should be the +// job of the service tests test('creates a service', async () => { const files = await sdl.files({ name: 'User', crud: false }) - expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( - loadFixture(path.join(serviceFixturesPath, 'singleWordService.js')) - ) -}) - -test('creates a service test', async () => { - const files = await sdl.files({ name: 'User', crud: false }) - - expect( - files['/path/to/project/api/src/services/users/users.test.js'] - ).toEqual( - loadFixture(path.join(serviceFixturesPath, 'singleWordService.test.js')) - ) -}) - -test('the generated service inherits crud setting', async () => { - const files = await sdl.files({ name: 'Post', crud: true }) - - expect( - files['/path/to/project/api/src/services/posts/posts.test.js'] - ).toEqual( - loadFixture(path.join(serviceFixturesPath, 'singleWordServiceCrud.test.js')) - ) + expect(files).toHaveProperty([ + '/path/to/project/api/src/services/users/users.js', + ]) + expect(files).toHaveProperty([ + '/path/to/project/api/src/services/users/users.test.js', + ]) }) test('creates a single word sdl file', async () => { diff --git a/packages/cli/src/commands/generate/sdl/sdl.js b/packages/cli/src/commands/generate/sdl/sdl.js index fc0ca1bb3139..14393aa0b4e5 100644 --- a/packages/cli/src/commands/generate/sdl/sdl.js +++ b/packages/cli/src/commands/generate/sdl/sdl.js @@ -9,6 +9,7 @@ import { generateTemplate, getSchema, getPaths, writeFilesTask } from 'src/lib' import c from 'src/lib/colors' import { files as serviceFiles } from '../service/service' +import { relationsForModel } from '../helpers' const IGNORE_FIELDS_FOR_INPUT = ['id', 'createdAt'] @@ -65,6 +66,7 @@ const sdlFromSchemaModel = async (name) => { query: querySDL(model).join('\n '), input: inputSDL(model, types).join('\n '), idType: idType(model), + relations: relationsForModel(model), } } else { throw new Error( @@ -74,7 +76,7 @@ const sdlFromSchemaModel = async (name) => { } export const files = async ({ name, crud }) => { - const { query, input, idType } = await sdlFromSchemaModel( + const { query, input, idType, relations } = await sdlFromSchemaModel( pascalcase(pluralize.singular(name)) ) @@ -95,7 +97,7 @@ export const files = async ({ name, crud }) => { ) return { [outputPath]: template, - ...(await serviceFiles({ name, crud })), + ...(await serviceFiles({ name, crud, relations })), } } diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.js new file mode 100644 index 000000000000..2f54fb24b2c6 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.js @@ -0,0 +1,5 @@ +import { db } from 'src/lib/db' + +export const userProfiles = () => { + return db.userProfile.findMany() +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.js new file mode 100644 index 000000000000..0c1f26ab30ed --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord.test.js @@ -0,0 +1,7 @@ +import { userProfiles } from './userProfiles' + +describe('userProfiles', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.js new file mode 100644 index 000000000000..8be74a61b2e7 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.js @@ -0,0 +1,30 @@ +import { db } from 'src/lib/db' + +export const userProfiles = () => { + return db.userProfile.findMany() +} + +export const userProfile = ({ id }) => { + return db.userProfile.findOne({ + where: { id }, + }) +} + +export const createUserProfile = ({ input }) => { + return db.userProfile.create({ + data: input, + }) +} + +export const updateUserProfile = ({ id, input }) => { + return db.userProfile.update({ + data: input, + where: { id }, + }) +} + +export const deleteUserProfile = ({ id }) => { + return db.userProfile.delete({ + where: { id }, + }) +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.js new file mode 100644 index 000000000000..0c1f26ab30ed --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/multiWord_crud.test.js @@ -0,0 +1,7 @@ +import { userProfiles } from './userProfiles' + +describe('userProfiles', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.js new file mode 100644 index 000000000000..b5bfed92100b --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.js @@ -0,0 +1,5 @@ +import { db } from 'src/lib/db' + +export const users = () => { + return db.user.findMany() +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.js new file mode 100644 index 000000000000..5a73412f0b9d --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord.test.js @@ -0,0 +1,7 @@ +import { users } from './users' + +describe('users', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.js new file mode 100644 index 000000000000..b1c72da817c6 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_belongsTo.js @@ -0,0 +1,9 @@ +import { db } from 'src/lib/db' + +export const users = () => { + return db.user.findMany() +} + +export const User = { + identity: (_obj, { root }) => db.user.findOne({ where: { id: root.id } }).identity(), +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.js new file mode 100644 index 000000000000..be53d32bf06f --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.js @@ -0,0 +1,30 @@ +import { db } from 'src/lib/db' + +export const posts = () => { + return db.post.findMany() +} + +export const post = ({ id }) => { + return db.post.findOne({ + where: { id }, + }) +} + +export const createPost = ({ input }) => { + return db.post.create({ + data: input, + }) +} + +export const updatePost = ({ id, input }) => { + return db.post.update({ + data: input, + where: { id }, + }) +} + +export const deletePost = ({ id }) => { + return db.post.delete({ + where: { id }, + }) +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.js new file mode 100644 index 000000000000..b5ff8afbdfc3 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_crud.test.js @@ -0,0 +1,7 @@ +import { posts } from './posts' + +describe('posts', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.js new file mode 100644 index 000000000000..200d1809af15 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_hasMany.js @@ -0,0 +1,9 @@ +import { db } from 'src/lib/db' + +export const users = () => { + return db.user.findMany() +} + +export const User = { + userProfiles: (_obj, { root }) => db.user.findOne({ where: { id: root.id } }).userProfiles(), +} diff --git a/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.js b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.js new file mode 100644 index 000000000000..1dfea708f072 --- /dev/null +++ b/packages/cli/src/commands/generate/service/__tests__/fixtures/singleWord_multiple.js @@ -0,0 +1,10 @@ +import { db } from 'src/lib/db' + +export const users = () => { + return db.user.findMany() +} + +export const User = { + userProfiles: (_obj, { root }) => db.user.findOne({ where: { id: root.id } }).userProfiles(), + identity: (_obj, { root }) => db.user.findOne({ where: { id: root.id } }).identity(), +} diff --git a/packages/cli/src/commands/generate/service/__tests__/service.test.js b/packages/cli/src/commands/generate/service/__tests__/service.test.js index 5a3608281f54..3ae77b8b6a07 100644 --- a/packages/cli/src/commands/generate/service/__tests__/service.test.js +++ b/packages/cli/src/commands/generate/service/__tests__/service.test.js @@ -4,71 +4,143 @@ import { loadGeneratorFixture } from 'src/lib/test' import * as service from '../service' test('returns exactly 2 files', async () => { - const files = await service.files({ name: 'User', crud: false }) + const files = await service.files({ + name: 'User', + crud: false, + relations: [], + }) expect(Object.keys(files).length).toEqual(2) }) test('creates a single word service file', async () => { - const files = await service.files({ name: 'User', crud: false }) + const files = await service.files({ + name: 'User', + crud: false, + relations: [], + }) expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( - loadGeneratorFixture('service', 'singleWordService.js') + loadGeneratorFixture('service', 'singleWord.js') ) }) test('creates a single word service test file', async () => { - const files = await service.files({ name: 'User', crud: false }) + const files = await service.files({ + name: 'User', + crud: false, + relations: null, + }) expect( files['/path/to/project/api/src/services/users/users.test.js'] - ).toEqual(loadGeneratorFixture('service', 'singleWordService.test.js')) + ).toEqual(loadGeneratorFixture('service', 'singleWord.test.js')) }) test('creates a multi word service file', async () => { - const files = await service.files({ name: 'UserProfile', crud: false }) + const files = await service.files({ + name: 'UserProfile', + crud: false, + relations: null, + }) expect( files['/path/to/project/api/src/services/userProfiles/userProfiles.js'] - ).toEqual(loadGeneratorFixture('service', 'multiWordService.js')) + ).toEqual(loadGeneratorFixture('service', 'multiWord.js')) }) test('creates a multi word service test file', async () => { - const files = await service.files({ name: 'UserProfile', crud: false }) + const files = await service.files({ + name: 'UserProfile', + crud: false, + relations: null, + }) expect( files['/path/to/project/api/src/services/userProfiles/userProfiles.test.js'] - ).toEqual(loadGeneratorFixture('service', 'multiWordService.test.js')) + ).toEqual(loadGeneratorFixture('service', 'multiWord.test.js')) }) test('creates a single word service file with CRUD actions', async () => { - const files = await service.files({ name: 'Post', crud: true }) + const files = await service.files({ + name: 'Post', + crud: true, + relations: null, + }) expect(files['/path/to/project/api/src/services/posts/posts.js']).toEqual( - loadGeneratorFixture('service', 'singleWordServiceCrud.js') + loadGeneratorFixture('service', 'singleWord_crud.js') ) }) test('creates a service test file with CRUD actions', async () => { - const files = await service.files({ name: 'Post', crud: true }) + const files = await service.files({ + name: 'Post', + crud: true, + relations: null, + }) expect( files['/path/to/project/api/src/services/posts/posts.test.js'] - ).toEqual(loadGeneratorFixture('service', 'singleWordServiceCrud.test.js')) + ).toEqual(loadGeneratorFixture('service', 'singleWord_crud.test.js')) }) test('creates a multi word service file with CRUD actions', async () => { - const files = await service.files({ name: 'UserProfile', crud: true }) + const files = await service.files({ + name: 'UserProfile', + crud: true, + relations: null, + }) expect( files['/path/to/project/api/src/services/userProfiles/userProfiles.js'] - ).toEqual(loadGeneratorFixture('service', 'multiWordServiceCrud.js')) + ).toEqual(loadGeneratorFixture('service', 'multiWord_crud.js')) }) test('creates a multi word service test file with CRUD actions', async () => { - const files = await service.files({ name: 'UserProfile', crud: true }) + const files = await service.files({ + name: 'UserProfile', + crud: true, + relations: null, + }) expect( files['/path/to/project/api/src/services/userProfiles/userProfiles.test.js'] - ).toEqual(loadGeneratorFixture('service', 'multiWordServiceCrud.test.js')) + ).toEqual(loadGeneratorFixture('service', 'multiWord_crud.test.js')) +}) + +test('creates a single word service file with a hasMany relation', async () => { + const files = await service.files({ + name: 'User', + crud: false, + relations: ['userProfiles'], + }) + + expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( + loadGeneratorFixture('service', 'singleWord_hasMany.js') + ) +}) + +test('creates a single word service file with a belongsTo relation', async () => { + const files = await service.files({ + name: 'User', + crud: false, + relations: ['identity'], + }) + + expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( + loadGeneratorFixture('service', 'singleWord_belongsTo.js') + ) +}) + +test('creates a single word service file with multiple relations', async () => { + const files = await service.files({ + name: 'User', + crud: false, + relations: ['userProfiles', 'identity'], + }) + + expect(files['/path/to/project/api/src/services/users/users.js']).toEqual( + loadGeneratorFixture('service', 'singleWord_multiple.js') + ) }) diff --git a/packages/cli/src/commands/generate/service/service.js b/packages/cli/src/commands/generate/service/service.js index 4706fba850b3..945ace05df2b 100644 --- a/packages/cli/src/commands/generate/service/service.js +++ b/packages/cli/src/commands/generate/service/service.js @@ -6,7 +6,7 @@ import { createYargsForComponentGeneration, } from '../helpers' -export const files = async ({ name, ...rest }) => { +export const files = async ({ name, relations, ...rest }) => { const componentName = camelcase(pluralize(name)) const serviceFile = templateForComponentFile({ name, @@ -14,7 +14,7 @@ export const files = async ({ name, ...rest }) => { apiPathSection: 'services', generator: 'service', templatePath: 'service.js.template', - templateVars: { ...rest }, + templateVars: { relations: relations || [], ...rest }, }) const testFile = templateForComponentFile({ name, @@ -23,7 +23,7 @@ export const files = async ({ name, ...rest }) => { apiPathSection: 'services', generator: 'service', templatePath: 'test.js.template', - templateVars: { ...rest }, + templateVars: { relations: relations || [], ...rest }, }) // Returns diff --git a/packages/cli/src/commands/generate/service/templates/service.js.template b/packages/cli/src/commands/generate/service/templates/service.js.template index 93310d1119de..0ad65cbd7a5e 100644 --- a/packages/cli/src/commands/generate/service/templates/service.js.template +++ b/packages/cli/src/commands/generate/service/templates/service.js.template @@ -27,4 +27,8 @@ export const delete${singularPascalName} = ({ id }) => { return db.${singularCamelName}.delete({ where: { id }, }) +}<% } %><% if (relations.length) { %> + +export const ${singularPascalName} = {<% relations.forEach(relation => { %> + ${relation}: (_obj, { root }) => db.${singularCamelName}.findOne({ where: { id: root.id } }).${relation}(),<% }) %> }<% } %> diff --git a/packages/cli/src/lib/test.js b/packages/cli/src/lib/test.js index e2fb5387a399..6bb88d181a7c 100644 --- a/packages/cli/src/lib/test.js +++ b/packages/cli/src/lib/test.js @@ -42,20 +42,6 @@ export const generatorsRootPath = path.join( 'generate' ) -export const sdlFixturesPath = path.join( - generatorsRootPath, - 'sdl', - '__tests__', - 'fixtures' -) - -export const serviceFixturesPath = path.join( - generatorsRootPath, - 'service', - '__tests__', - 'fixtures' -) - // Loads the fixture for a generator by assuming a lot of the path structure automatically: // // loadGeneratorFixture('scaffold', 'NamePage.js') From a57face8e13e21afceae182084b11361b1005b99 Mon Sep 17 00:00:00 2001 From: David S Price Date: Sat, 11 Apr 2020 09:47:09 -0700 Subject: [PATCH 19/55] upgrade eslint-plugin-import, misc eslint packges --- packages/eslint-config/package.json | 8 ++--- yarn.lock | 53 ++++++++++++++++------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 8819f05d758f..803b57184c5a 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -5,20 +5,20 @@ "license": "MIT", "dependencies": { "@redwoodjs/eslint-plugin-redwood": "^0.4.0", - "@typescript-eslint/eslint-plugin": "^2.25.0", - "@typescript-eslint/parser": "^2.25.0", + "@typescript-eslint/eslint-plugin": "^2.27.0", + "@typescript-eslint/parser": "^2.27.0", "babel-eslint": "^10.1.0", "eslint": "6.8.0", "eslint-config-prettier": "^6.10.1", "eslint-import-resolver-babel-module": "^5.1.2", "eslint-plugin-babel": "^5.3.0", - "eslint-plugin-import": "^2.20.1", + "eslint-plugin-import": "^2.20.2", "eslint-plugin-jest-dom": "^2.0.1", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.19.0", "eslint-plugin-react-hooks": "^3.0.0", - "prettier": "^2.0.2" + "prettier": "^2.0.4" }, "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649" } diff --git a/yarn.lock b/yarn.lock index 73af449a46aa..c78d19b13b36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2780,40 +2780,40 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.25.0.tgz#0b60917332f20dcff54d0eb9be2a9e9f4c9fbd02" - integrity sha512-W2YyMtjmlrOjtXc+FtTelVs9OhuR6OlYc4XKIslJ8PUJOqgYYAPRJhAqkYRQo3G4sjvG8jSodsNycEn4W2gHUw== +"@typescript-eslint/eslint-plugin@^2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.27.0.tgz#e479cdc4c9cf46f96b4c287755733311b0d0ba4b" + integrity sha512-/my+vVHRN7zYgcp0n4z5A6HAK7bvKGBiswaM5zIlOQczsxj/aiD7RcgD+dvVFuwFaGh5+kM7XA6Q6PN0bvb1tw== dependencies: - "@typescript-eslint/experimental-utils" "2.25.0" + "@typescript-eslint/experimental-utils" "2.27.0" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.25.0.tgz#13691c4fe368bd377b1e5b1e4ad660b220bf7714" - integrity sha512-0IZ4ZR5QkFYbaJk+8eJ2kYeA+1tzOE1sBjbwwtSV85oNWYUBep+EyhlZ7DLUCyhMUGuJpcCCFL0fDtYAP1zMZw== +"@typescript-eslint/experimental-utils@2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a" + integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.25.0" + "@typescript-eslint/typescript-estree" "2.27.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.25.0.tgz#abfb3d999084824d9a756d9b9c0f36fba03adb76" - integrity sha512-mccBLaBSpNVgp191CP5W+8U1crTyXsRziWliCqzj02kpxdjKMvFHGJbK33NroquH3zB/gZ8H511HEsJBa2fNEg== +"@typescript-eslint/parser@^2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.27.0.tgz#d91664335b2c46584294e42eb4ff35838c427287" + integrity sha512-HFUXZY+EdwrJXZo31DW4IS1ujQW3krzlRjBrFRrJcMDh0zCu107/nRfhk/uBasO8m0NVDbBF5WZKcIUMRO7vPg== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.25.0" - "@typescript-eslint/typescript-estree" "2.25.0" + "@typescript-eslint/experimental-utils" "2.27.0" + "@typescript-eslint/typescript-estree" "2.27.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.25.0.tgz#b790497556734b7476fa7dd3fa539955a5c79e2c" - integrity sha512-VUksmx5lDxSi6GfmwSK7SSoIKSw9anukWWNitQPqt58LuYrKalzsgeuignbqnB+rK/xxGlSsCy8lYnwFfB6YJg== +"@typescript-eslint/typescript-estree@2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8" + integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -6018,10 +6018,10 @@ eslint-plugin-babel@^5.3.0: dependencies: eslint-rule-composer "^0.3.0" -eslint-plugin-import@^2.20.1: - version "2.20.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3" - integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw== +eslint-plugin-import@^2.20.2: + version "2.20.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" + integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== dependencies: array-includes "^3.0.3" array.prototype.flat "^1.2.1" @@ -10868,6 +10868,11 @@ prettier@^2.0.1, prettier@^2.0.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08" integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg== +prettier@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.4.tgz#2d1bae173e355996ee355ec9830a7a1ee05457ef" + integrity sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w== + pretty-error@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" From f2441361a4a7ffa075bb0133a2f201eb14438260 Mon Sep 17 00:00:00 2001 From: David S Price Date: Sat, 11 Apr 2020 10:02:44 -0700 Subject: [PATCH 20/55] Revert "upgrade eslint-plugin-import, misc eslint packges" This reverts commit a57face8e13e21afceae182084b11361b1005b99. --- packages/eslint-config/package.json | 8 ++--- yarn.lock | 53 +++++++++++++---------------- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 803b57184c5a..8819f05d758f 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -5,20 +5,20 @@ "license": "MIT", "dependencies": { "@redwoodjs/eslint-plugin-redwood": "^0.4.0", - "@typescript-eslint/eslint-plugin": "^2.27.0", - "@typescript-eslint/parser": "^2.27.0", + "@typescript-eslint/eslint-plugin": "^2.25.0", + "@typescript-eslint/parser": "^2.25.0", "babel-eslint": "^10.1.0", "eslint": "6.8.0", "eslint-config-prettier": "^6.10.1", "eslint-import-resolver-babel-module": "^5.1.2", "eslint-plugin-babel": "^5.3.0", - "eslint-plugin-import": "^2.20.2", + "eslint-plugin-import": "^2.20.1", "eslint-plugin-jest-dom": "^2.0.1", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.19.0", "eslint-plugin-react-hooks": "^3.0.0", - "prettier": "^2.0.4" + "prettier": "^2.0.2" }, "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649" } diff --git a/yarn.lock b/yarn.lock index c78d19b13b36..73af449a46aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2780,40 +2780,40 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.27.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.27.0.tgz#e479cdc4c9cf46f96b4c287755733311b0d0ba4b" - integrity sha512-/my+vVHRN7zYgcp0n4z5A6HAK7bvKGBiswaM5zIlOQczsxj/aiD7RcgD+dvVFuwFaGh5+kM7XA6Q6PN0bvb1tw== +"@typescript-eslint/eslint-plugin@^2.25.0": + version "2.25.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.25.0.tgz#0b60917332f20dcff54d0eb9be2a9e9f4c9fbd02" + integrity sha512-W2YyMtjmlrOjtXc+FtTelVs9OhuR6OlYc4XKIslJ8PUJOqgYYAPRJhAqkYRQo3G4sjvG8jSodsNycEn4W2gHUw== dependencies: - "@typescript-eslint/experimental-utils" "2.27.0" + "@typescript-eslint/experimental-utils" "2.25.0" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.27.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a" - integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw== +"@typescript-eslint/experimental-utils@2.25.0": + version "2.25.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.25.0.tgz#13691c4fe368bd377b1e5b1e4ad660b220bf7714" + integrity sha512-0IZ4ZR5QkFYbaJk+8eJ2kYeA+1tzOE1sBjbwwtSV85oNWYUBep+EyhlZ7DLUCyhMUGuJpcCCFL0fDtYAP1zMZw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.27.0" + "@typescript-eslint/typescript-estree" "2.25.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^2.27.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.27.0.tgz#d91664335b2c46584294e42eb4ff35838c427287" - integrity sha512-HFUXZY+EdwrJXZo31DW4IS1ujQW3krzlRjBrFRrJcMDh0zCu107/nRfhk/uBasO8m0NVDbBF5WZKcIUMRO7vPg== +"@typescript-eslint/parser@^2.25.0": + version "2.25.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.25.0.tgz#abfb3d999084824d9a756d9b9c0f36fba03adb76" + integrity sha512-mccBLaBSpNVgp191CP5W+8U1crTyXsRziWliCqzj02kpxdjKMvFHGJbK33NroquH3zB/gZ8H511HEsJBa2fNEg== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.27.0" - "@typescript-eslint/typescript-estree" "2.27.0" + "@typescript-eslint/experimental-utils" "2.25.0" + "@typescript-eslint/typescript-estree" "2.25.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.27.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8" - integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg== +"@typescript-eslint/typescript-estree@2.25.0": + version "2.25.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.25.0.tgz#b790497556734b7476fa7dd3fa539955a5c79e2c" + integrity sha512-VUksmx5lDxSi6GfmwSK7SSoIKSw9anukWWNitQPqt58LuYrKalzsgeuignbqnB+rK/xxGlSsCy8lYnwFfB6YJg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -6018,10 +6018,10 @@ eslint-plugin-babel@^5.3.0: dependencies: eslint-rule-composer "^0.3.0" -eslint-plugin-import@^2.20.2: - version "2.20.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" - integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== +eslint-plugin-import@^2.20.1: + version "2.20.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3" + integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw== dependencies: array-includes "^3.0.3" array.prototype.flat "^1.2.1" @@ -10868,11 +10868,6 @@ prettier@^2.0.1, prettier@^2.0.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08" integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg== -prettier@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.4.tgz#2d1bae173e355996ee355ec9830a7a1ee05457ef" - integrity sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w== - pretty-error@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" From 7650574f2110afe60edacbfd3e78ab845b19425a Mon Sep 17 00:00:00 2001 From: David S Price Date: Sat, 11 Apr 2020 10:34:08 -0700 Subject: [PATCH 21/55] upgrade eslint-plugin-import, @typescript-eslint/* --- packages/eslint-config/package.json | 6 ++-- yarn.lock | 48 ++++++++++++++--------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 8819f05d758f..c8ff3a6fd23c 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -5,14 +5,14 @@ "license": "MIT", "dependencies": { "@redwoodjs/eslint-plugin-redwood": "^0.4.0", - "@typescript-eslint/eslint-plugin": "^2.25.0", - "@typescript-eslint/parser": "^2.25.0", + "@typescript-eslint/eslint-plugin": "^2.27.0", + "@typescript-eslint/parser": "^2.27.0", "babel-eslint": "^10.1.0", "eslint": "6.8.0", "eslint-config-prettier": "^6.10.1", "eslint-import-resolver-babel-module": "^5.1.2", "eslint-plugin-babel": "^5.3.0", - "eslint-plugin-import": "^2.20.1", + "eslint-plugin-import": "^2.20.2", "eslint-plugin-jest-dom": "^2.0.1", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.2", diff --git a/yarn.lock b/yarn.lock index 73af449a46aa..0855d9454747 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2780,40 +2780,40 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.25.0.tgz#0b60917332f20dcff54d0eb9be2a9e9f4c9fbd02" - integrity sha512-W2YyMtjmlrOjtXc+FtTelVs9OhuR6OlYc4XKIslJ8PUJOqgYYAPRJhAqkYRQo3G4sjvG8jSodsNycEn4W2gHUw== +"@typescript-eslint/eslint-plugin@^2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.27.0.tgz#e479cdc4c9cf46f96b4c287755733311b0d0ba4b" + integrity sha512-/my+vVHRN7zYgcp0n4z5A6HAK7bvKGBiswaM5zIlOQczsxj/aiD7RcgD+dvVFuwFaGh5+kM7XA6Q6PN0bvb1tw== dependencies: - "@typescript-eslint/experimental-utils" "2.25.0" + "@typescript-eslint/experimental-utils" "2.27.0" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.25.0.tgz#13691c4fe368bd377b1e5b1e4ad660b220bf7714" - integrity sha512-0IZ4ZR5QkFYbaJk+8eJ2kYeA+1tzOE1sBjbwwtSV85oNWYUBep+EyhlZ7DLUCyhMUGuJpcCCFL0fDtYAP1zMZw== +"@typescript-eslint/experimental-utils@2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a" + integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.25.0" + "@typescript-eslint/typescript-estree" "2.27.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.25.0.tgz#abfb3d999084824d9a756d9b9c0f36fba03adb76" - integrity sha512-mccBLaBSpNVgp191CP5W+8U1crTyXsRziWliCqzj02kpxdjKMvFHGJbK33NroquH3zB/gZ8H511HEsJBa2fNEg== +"@typescript-eslint/parser@^2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.27.0.tgz#d91664335b2c46584294e42eb4ff35838c427287" + integrity sha512-HFUXZY+EdwrJXZo31DW4IS1ujQW3krzlRjBrFRrJcMDh0zCu107/nRfhk/uBasO8m0NVDbBF5WZKcIUMRO7vPg== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.25.0" - "@typescript-eslint/typescript-estree" "2.25.0" + "@typescript-eslint/experimental-utils" "2.27.0" + "@typescript-eslint/typescript-estree" "2.27.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.25.0": - version "2.25.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.25.0.tgz#b790497556734b7476fa7dd3fa539955a5c79e2c" - integrity sha512-VUksmx5lDxSi6GfmwSK7SSoIKSw9anukWWNitQPqt58LuYrKalzsgeuignbqnB+rK/xxGlSsCy8lYnwFfB6YJg== +"@typescript-eslint/typescript-estree@2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8" + integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -6018,10 +6018,10 @@ eslint-plugin-babel@^5.3.0: dependencies: eslint-rule-composer "^0.3.0" -eslint-plugin-import@^2.20.1: - version "2.20.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3" - integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw== +eslint-plugin-import@^2.20.2: + version "2.20.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" + integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== dependencies: array-includes "^3.0.3" array.prototype.flat "^1.2.1" From ed0eb079ff9f7a67ae7f2ff49d4aef0649a1510e Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Sat, 11 Apr 2020 16:38:42 -0400 Subject: [PATCH 22/55] Add docs for local Postgres and connection pooling. --- docs/connectionPooling.md | 29 +++++++++++++++++++ docs/localPostgresSetup.md | 57 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 docs/connectionPooling.md create mode 100644 docs/localPostgresSetup.md diff --git a/docs/connectionPooling.md b/docs/connectionPooling.md new file mode 100644 index 000000000000..bfbf6d7bec5e --- /dev/null +++ b/docs/connectionPooling.md @@ -0,0 +1,29 @@ +# Connection Pooling + +Production Redwood apps should enable connection pooling in order to properly scale with your Serverless functions. + +## Heroku +For Postgres, see [Postgres Connection Pooling](https://devcenter.heroku.com/articles/postgres-connection-pooling). + +Heroku does not officially support MySQL. + + +## Digital Ocean +For Postgres, see [How to Manage Connection Pools](https://www.digitalocean.com/docs/databases/postgresql/how-to/manage-connection-pools) + +Connection Pooling for MySQL is not yet supported. + +## AWS +Use [Amazon RDS Proxy] https://aws.amazon.com/rds/proxy for MySQL or RDS PostgreSQL. + + +## Why Connection Pooling? + +Relational databases have a maximum number of concurrent client connections. + +* Postgres allows 100 by default +* MySQL allows 151 by default + +In a traditional server environment, you would need a large amount of traffic (and therefore web servers) to exhaust these connections, since each web server instance typically leverages a single connection. + +In a Serverless environment, each function connects directly to the database, which can exhaust limits quickly. To prevent connection errors, you should add a connection pooling service in front of your database. Think of it as a load balancer. \ No newline at end of file diff --git a/docs/localPostgresSetup.md b/docs/localPostgresSetup.md new file mode 100644 index 000000000000..1eb4efc8be95 --- /dev/null +++ b/docs/localPostgresSetup.md @@ -0,0 +1,57 @@ +# Local Postgres Setup + +RedwoodJS uses a SQLite database by default. While SQLite makes local development easy, you're +likely going to want to run the same database setup you use on production. Here's how to setup +Postgres. + +## Install Postgres + +Ensure you have Postgres installed and running on your machine. If you're on a Mac, we recommend +Homebrew: + +```bash +brew install postgres +``` + +Follow the instructions provided. If you're using another platform, See +[postgresql.org/download](https://www.postgresql.org/download/). + +## Update the Prisma Schema + +Tell Prisma to use a Postgres database instead of SQLite by updating the `provider` attribute in your +`schema.prisma` file: + +```prisma +// prisma/schema.prisma +datasource DS { + provider = "postgres" + url = env("DATABASE_URL") +} +``` + +Add a `DATABASE_URL` to your `.env` file with the URL of the database you'd like to use locally. The +following example uses `redwoodblog_dev` for the database. It also has `postgres` setup as a +superuser for ease of use. + +```env +# .env +DATABASE_URL="postgresql://postgres@localhost/redwoodblog_dev?connection_limit=1" +``` + +Note the `connection_limit` parameter. This is [recommended by Prisma](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/deployment#recommended-connection-limit) when working with +relational databases in a Serverless context. You should also append this parameter to your production +`DATABASE_URL` when configuring your deployments. + +If you've already created migrations using SQLite, you just need to run migrations again: + +```bash +yarn rw db up +``` + +If you haven't created migrations yet, use `save`: + +```bash +yarn rw db save +``` + +Both commands will create and migrate the Postres database you specified in your `.env`. \ No newline at end of file From 06786403c3b132dd0c75f6b99b626b45a6b0ae79 Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Sun, 12 Apr 2020 10:21:13 +0200 Subject: [PATCH 23/55] Rename clean to clean-dist. --- packages/cli/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 9dccbcf3f8ce..b83d0d2187a5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,9 +34,9 @@ "@types/node-fetch": "^2.5.5" }, "scripts": { - "build": "yarn build:all && yarn build:clean", - "build:all": "yarn cross-env NODE_ENV=production yarn babel src -d dist --delete-dir-on-start --copy-files --no-copy-ignored", - "build:clean": "yarn rimraf 'dist/**/__tests__'", + "build": "yarn build:js && yarn build:clean-dist", + "build:js": "yarn cross-env NODE_ENV=production yarn babel src -d dist --delete-dir-on-start --copy-files --no-copy-ignored", + "build:clean-dist": "yarn rimraf 'dist/**/__tests__'", "build:watch": "nodemon --ignore dist --exec 'yarn build'", "test": "jest", "test:watch": "yarn test --watch" From 982365442d055f8403d1a6ed6bfbee3bb763e57e Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Sun, 12 Apr 2020 10:22:01 +0200 Subject: [PATCH 24/55] Commit this vscode color change. --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 68590a898082..517404152a46 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,8 @@ "workbench.colorCustomizations": { "statusBar.background": "#b85833", "statusBarItem.hoverBackground": "#ce7350", - "statusBar.foreground": "#e7e7e7" + "statusBar.foreground": "#e7e7e7", + "statusBar.border": "#b85833" }, "peacock.color": "#b85833", } \ No newline at end of file From b59966c8c58990dee20f8081cb5beae5c1bca7a0 Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Sun, 12 Apr 2020 11:27:34 +0200 Subject: [PATCH 25/55] Normalize path strings for windows tests. --- .../generate/__tests__/helpers.test.js | 16 +++-- .../scaffold/__tests__/scaffold.test.js | 70 ++++++++++++++----- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/packages/cli/src/commands/generate/__tests__/helpers.test.js b/packages/cli/src/commands/generate/__tests__/helpers.test.js index 01f347117ff7..a526a6d39c60 100644 --- a/packages/cli/src/commands/generate/__tests__/helpers.test.js +++ b/packages/cli/src/commands/generate/__tests__/helpers.test.js @@ -1,3 +1,5 @@ +import path from 'path' + global.__dirname = __dirname import {} from 'src/lib/test' @@ -7,7 +9,9 @@ const PAGE_TEMPLATE_OUTPUT = `const FooBarPage = () => { return (

FooBarPage

-

Find me in ./web/src/pages/FooBarPage/FooBarPage.js

+

Find me in ./${path.normalize( + 'web/src/pages/FooBarPage/FooBarPage.js' + )}

) } @@ -28,7 +32,7 @@ test('templateForComponentFile creates a proper output path for files', () => { }) expect(output[0]).toEqual( - '/path/to/project/web/src/pages/FooBarPage/FooBarPage.js' + path.normalize('/path/to/project/web/src/pages/FooBarPage/FooBarPage.js') ) }) }) @@ -43,7 +47,7 @@ test('templateForComponentFile can create a path in /web', () => { }) expect(output[0]).toEqual( - '/path/to/project/web/src/pages/HomePage/HomePage.js' + path.normalize('/path/to/project/web/src/pages/HomePage/HomePage.js') ) }) @@ -57,7 +61,7 @@ test('templateForComponentFile can create a path in /api', () => { }) expect(output[0]).toEqual( - '/path/to/project/api/src/services/HomePage/HomePage.js' + path.normalize('/path/to/project/api/src/services/HomePage/HomePage.js') ) }) @@ -71,7 +75,7 @@ test('templateForComponentFile can override generated component name', () => { }) expect(output[0]).toEqual( - '/path/to/project/web/src/pages/Hobbiton/Hobbiton.js' + path.normalize('/path/to/project/web/src/pages/Hobbiton/Hobbiton.js') ) }) @@ -86,7 +90,7 @@ test('templateForComponentFile can override file extension', () => { }) expect(output[0]).toEqual( - '/path/to/project/web/src/pages/HomePage/HomePage.txt' + path.normalize('/path/to/project/web/src/pages/HomePage/HomePage.txt') ) }) diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js b/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js index 66a48a0f5ba7..a293db893bc9 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/scaffold.test.js @@ -19,7 +19,7 @@ test('returns exactly 16 files', () => { test('creates an sdl', () => { expect(files).toHaveProperty([ - '/path/to/project/api/src/graphql/posts.sdl.js', + path.normalize('/path/to/project/api/src/graphql/posts.sdl.js'), ]) }) @@ -27,20 +27,22 @@ test('creates an sdl', () => { test('creates a service', () => { expect(files).toHaveProperty([ - '/path/to/project/api/src/services/posts/posts.js', + path.normalize('/path/to/project/api/src/services/posts/posts.js'), ]) }) test('creates a service test', () => { expect(files).toHaveProperty([ - '/path/to/project/api/src/services/posts/posts.test.js', + path.normalize('/path/to/project/api/src/services/posts/posts.test.js'), ]) }) // styles test('creates a stylesheet', () => { - expect(files['/path/to/project/web/src/scaffold.css']).toEqual( + expect( + files[path.normalize('/path/to/project/web/src/scaffold.css')] + ).toEqual( loadGeneratorFixture('scaffold', path.join('assets', 'scaffold.css')) ) }) @@ -49,7 +51,11 @@ test('creates a stylesheet', () => { test('creates a layout', async () => { expect( - files['/path/to/project/web/src/layouts/PostsLayout/PostsLayout.js'] + files[ + path.normalize( + '/path/to/project/web/src/layouts/PostsLayout/PostsLayout.js' + ) + ] ).toEqual(loadGeneratorFixture('scaffold', path.join('layouts', 'layout.js'))) }) @@ -57,7 +63,11 @@ test('creates a layout', async () => { test('creates a edit page', async () => { expect( - files['/path/to/project/web/src/pages/EditPostPage/EditPostPage.js'] + files[ + path.normalize( + '/path/to/project/web/src/pages/EditPostPage/EditPostPage.js' + ) + ] ).toEqual(loadGeneratorFixture('scaffold', path.join('pages', 'editPage.js'))) }) @@ -76,16 +86,20 @@ test('creates a new page', async () => { }) test('creates a show page', async () => { - expect(files['/path/to/project/web/src/pages/PostPage/PostPage.js']).toEqual( - loadGeneratorFixture('scaffold', path.join('pages', 'showPage.js')) - ) + expect( + files[path.normalize('/path/to/project/web/src/pages/PostPage/PostPage.js')] + ).toEqual(loadGeneratorFixture('scaffold', path.join('pages', 'showPage.js'))) }) // Cells test('creates an edit cell', async () => { expect( - files['/path/to/project/web/src/components/EditPostCell/EditPostCell.js'] + files[ + path.normalize( + '/path/to/project/web/src/components/EditPostCell/EditPostCell.js' + ) + ] ).toEqual( loadGeneratorFixture('scaffold', path.join('components', 'editCell.js')) ) @@ -101,7 +115,9 @@ test('creates an index cell', async () => { test('creates a show cell', async () => { expect( - files['/path/to/project/web/src/components/PostCell/PostCell.js'] + files[ + path.normalize('/path/to/project/web/src/components/PostCell/PostCell.js') + ] ).toEqual( loadGeneratorFixture('scaffold', path.join('components', 'showCell.js')) ) @@ -111,7 +127,9 @@ test('creates a show cell', async () => { test('creates a form component', async () => { expect( - files['/path/to/project/web/src/components/PostForm/PostForm.js'] + files[ + path.normalize('/path/to/project/web/src/components/PostForm/PostForm.js') + ] ).toEqual( loadGeneratorFixture('scaffold', path.join('components', 'form.js')) ) @@ -125,12 +143,16 @@ test('creates an index component', async () => { test('creates a new component', async () => { expect( - files['/path/to/project/web/src/components/NewPost/NewPost.js'] + files[ + path.normalize('/path/to/project/web/src/components/NewPost/NewPost.js') + ] ).toEqual(loadGeneratorFixture('scaffold', path.join('components', 'new.js'))) }) test('creates a show component', async () => { - expect(files['/path/to/project/web/src/components/Post/Post.js']).toEqual( + expect( + files[path.normalize('/path/to/project/web/src/components/Post/Post.js')] + ).toEqual( loadGeneratorFixture('scaffold', path.join('components', 'show.js')) ) }) @@ -161,7 +183,9 @@ test('the GraphQL in the index query does not contain object types', async () => const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) const cell = userProfileFiles[ - '/path/to/project/web/src/components/UserProfilesCell/UserProfilesCell.js' + path.normalize( + '/path/to/project/web/src/components/UserProfilesCell/UserProfilesCell.js' + ) ] const query = cell.match(/(userProfiles.*?\})/s)[1] @@ -172,7 +196,9 @@ test('the GraphQL in the show query does not contain object types', async () => const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) const cell = userProfileFiles[ - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + path.normalize( + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + ) ] const query = cell.match(/(userProfile.*?\})/s)[1] @@ -183,7 +209,9 @@ test('the GraphQL in the edit query does not contain object types', async () => const userProfileFiles = await scaffold.files({ model: 'UserProfile' }) const cell = userProfileFiles[ - '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + path.normalize( + '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + ) ] const query = cell.match(/(userProfile.*?\})/s)[1] @@ -197,7 +225,9 @@ test('creates a new component with int foreign keys converted in onSave', async expect( foreignKeyFiles[ - '/path/to/project/web/src/components/NewUserProfile/NewUserProfile.js' + path.normalize( + '/path/to/project/web/src/components/NewUserProfile/NewUserProfile.js' + ) ] ).toEqual( loadGeneratorFixture( @@ -212,7 +242,9 @@ test('creates an edit component with int foreign keys converted in onSave', asyn expect( foreignKeyFiles[ - '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + path.normalize( + '/path/to/project/web/src/components/EditUserProfileCell/EditUserProfileCell.js' + ) ] ).toEqual( loadGeneratorFixture( From cb5c7ee85caa0639471b61e9fde8c6c87e255f8b Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Mon, 13 Apr 2020 10:43:25 +0200 Subject: [PATCH 26/55] Build pacakges before publishing. --- packages/api/package.json | 1 + packages/cli/package.json | 1 + packages/core/package.json | 1 + packages/create-redwood-app/package.json | 1 + packages/dev-server/package.json | 3 ++- packages/eslint-plugin-redwood/package.json | 1 + packages/internal/package.json | 1 + packages/router/package.json | 1 + packages/web/package.json | 1 + 9 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/api/package.json b/packages/api/package.json index ff2cdbb87bda..8d4ea4097289 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -40,6 +40,7 @@ "build": "yarn build:js && yarn build:types && yarn move-cli dist/importAll.macro.js ./importAll.macro.js", "build:js": "yarn del-cli importAll.macro.js && yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start --extensions \".js,.ts\" --source-maps inline", "build:types": "tsc --declaration --emitDeclarationOnly", + "prepublishOnly": "yarn build", "build:watch": "nodemon --watch src -e ts,js --ignore dist --exec 'yarn build'", "test": "yarn jest", "test:watch": "yarn test --watch" diff --git a/packages/cli/package.json b/packages/cli/package.json index b83d0d2187a5..e0e24f196154 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -37,6 +37,7 @@ "build": "yarn build:js && yarn build:clean-dist", "build:js": "yarn cross-env NODE_ENV=production yarn babel src -d dist --delete-dir-on-start --copy-files --no-copy-ignored", "build:clean-dist": "yarn rimraf 'dist/**/__tests__'", + "prepublishOnly": "yarn build", "build:watch": "nodemon --ignore dist --exec 'yarn build'", "test": "jest", "test:watch": "yarn test --watch" diff --git a/packages/core/package.json b/packages/core/package.json index 2fee50a3b612..ed52ce63c4e9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -59,6 +59,7 @@ "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649", "scripts": { "build": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start", + "prepublishOnly": "yarn build", "build:watch": "nodemon --ignore dist --exec 'yarn build'" } } diff --git a/packages/create-redwood-app/package.json b/packages/create-redwood-app/package.json index f5c06ec6ffd7..959b26f53624 100644 --- a/packages/create-redwood-app/package.json +++ b/packages/create-redwood-app/package.json @@ -19,6 +19,7 @@ }, "scripts": { "build": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start", + "prepublishOnly": "yarn build", "build:watch": "nodemon --ignore dist --exec 'yarn build'" }, "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649" diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 342402297871..7cb0e2182557 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -28,7 +28,8 @@ "@types/require-dir": "^1.0.0" }, "scripts": { - "build": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start --extensions \".ts\" --source-maps inline" + "build": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start --extensions \".ts\" --source-maps inline", + "prepublishOnly": "yarn build" }, "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649" } diff --git a/packages/eslint-plugin-redwood/package.json b/packages/eslint-plugin-redwood/package.json index 6386246edb60..059e092ed8a3 100644 --- a/packages/eslint-plugin-redwood/package.json +++ b/packages/eslint-plugin-redwood/package.json @@ -12,6 +12,7 @@ "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649", "scripts": { "build": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start", + "prepublishOnly": "yarn build", "build:watch": "nodemon --ignore dist --exec 'yarn build'" } } diff --git a/packages/internal/package.json b/packages/internal/package.json index 0e4116717062..33f916f67ff4 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -23,6 +23,7 @@ "build": "yarn build:js && yarn build:types", "build:js": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start --extensions \".js,.ts\" --source-maps inline", "build:types": "tsc --declaration --emitDeclarationOnly", + "prepublishOnly": "yarn build", "build:watch": "nodemon --ignore dist --exec 'yarn build'", "test": "jest", "test:watch": "yarn test --watch" diff --git a/packages/router/package.json b/packages/router/package.json index 55970a000fab..d80dc1640fa1 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -14,6 +14,7 @@ }, "scripts": { "build": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start", + "prepublishOnly": "yarn build", "build:watch": "nodemon --ignore dist --exec 'yarn build'", "test": "yarn jest src", "test:watch": "yarn test --watch" diff --git a/packages/web/package.json b/packages/web/package.json index 4b281d66104e..2e357d829d1e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -26,6 +26,7 @@ }, "scripts": { "build": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start", + "prepublishOnly": "yarn build", "build:watch": "nodemon --ignore dist --exec 'yarn build'" }, "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649" From 05907e4227dd504551ff40802a39f20ed0bad75a Mon Sep 17 00:00:00 2001 From: Dominic Chapman Date: Mon, 13 Apr 2020 17:47:05 +0100 Subject: [PATCH 27/55] wrap setError in useEffect (#411) --- packages/web/src/form/form.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/web/src/form/form.js b/packages/web/src/form/form.js index 60b1d7c37673..a54d6d35eb91 100644 --- a/packages/web/src/form/form.js +++ b/packages/web/src/form/form.js @@ -1,5 +1,5 @@ import { useForm, FormContext, useFormContext } from 'react-hook-form' -import { useContext } from 'react' +import { useContext, useEffect } from 'react' const DEFAULT_MESSAGES = { required: 'is required', @@ -23,9 +23,13 @@ const inputTagProps = (props) => { // eslint-disable-next-line react-hooks/rules-of-hooks const fieldErrorsContext = useContext(FieldErrorContext) const contextError = fieldErrorsContext[props.name] - if (contextError) { - setError(props.name, 'server', contextError) - } + + // eslint-disable-next-line react-hooks/rules-of-hooks + useEffect(() => { + if (contextError) { + setError(props.name, 'server', contextError) + } + }, [contextError, props.name, setError]) // any errors on this field const validationError = errors[props.name] From 8c30fa555dc26527a86daa4d5375107db90bb97e Mon Sep 17 00:00:00 2001 From: David Price Date: Mon, 13 Apr 2020 12:20:14 -0700 Subject: [PATCH 28/55] Update localPostgresSetup.md simple spelling edit --- docs/localPostgresSetup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/localPostgresSetup.md b/docs/localPostgresSetup.md index 1eb4efc8be95..db752168c6e2 100644 --- a/docs/localPostgresSetup.md +++ b/docs/localPostgresSetup.md @@ -1,7 +1,7 @@ # Local Postgres Setup RedwoodJS uses a SQLite database by default. While SQLite makes local development easy, you're -likely going to want to run the same database setup you use on production. Here's how to setup +likely going to want to run the same database setup you use on production. Here's how to set up Postgres. ## Install Postgres @@ -54,4 +54,4 @@ If you haven't created migrations yet, use `save`: yarn rw db save ``` -Both commands will create and migrate the Postres database you specified in your `.env`. \ No newline at end of file +Both commands will create and migrate the Postres database you specified in your `.env`. From 309a394a6bf6f5b8fd89075b375c96a54a4c2bd3 Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Mon, 13 Apr 2020 15:44:58 -0400 Subject: [PATCH 29/55] Remove code comment in localPostgresSetup.md This was tripping up the HTML parser and causing it to add `.env` to the TOC. --- docs/localPostgresSetup.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/localPostgresSetup.md b/docs/localPostgresSetup.md index db752168c6e2..16fd7a8f1d36 100644 --- a/docs/localPostgresSetup.md +++ b/docs/localPostgresSetup.md @@ -34,7 +34,6 @@ following example uses `redwoodblog_dev` for the database. It also has `postgres superuser for ease of use. ```env -# .env DATABASE_URL="postgresql://postgres@localhost/redwoodblog_dev?connection_limit=1" ``` From 81e93b27bec4f2ef1655a5d0c62ad1e0099247ad Mon Sep 17 00:00:00 2001 From: Rob Cameron Date: Mon, 13 Apr 2020 15:24:37 -0700 Subject: [PATCH 30/55] Use refetchQueries to update data after a delete --- .../scaffold/__tests__/fixtures/components/index.js | 8 ++------ .../scaffold/templates/components/Names.js.template | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/index.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/index.js index 43f45c93da47..6f1b346ec061 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/index.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/index.js @@ -28,15 +28,11 @@ const timeTag = (datetime) => { } const PostsList = ({ posts }) => { - const [deletePost] = useMutation(DELETE_POST_MUTATION, { - onCompleted: () => { - location.reload() - }, - }) + const [deletePost] = useMutation(DELETE_POST_MUTATION) const onDeleteClick = (id) => { if (confirm('Are you sure you want to delete post ' + id + '?')) { - deletePost({ variables: { id } }) + deletePost({ variables: { id }, refetchQueries: ['POSTS'] }) } } diff --git a/packages/cli/src/commands/generate/scaffold/templates/components/Names.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/Names.js.template index 808f336165e8..7c955f372ed3 100644 --- a/packages/cli/src/commands/generate/scaffold/templates/components/Names.js.template +++ b/packages/cli/src/commands/generate/scaffold/templates/components/Names.js.template @@ -28,15 +28,11 @@ const timeTag = (datetime) => { } const ${pluralPascalName}List = ({ ${pluralCamelName} }) => { - const [delete${singularPascalName}] = useMutation(DELETE_POST_MUTATION, { - onCompleted: () => { - location.reload() - }, - }) + const [delete${singularPascalName}] = useMutation(DELETE_POST_MUTATION) const onDeleteClick = (id) => { if (confirm('Are you sure you want to delete ${singularCamelName} ' + id + '?')) { - delete${singularPascalName}({ variables: { id } }) + delete${singularPascalName}({ variables: { id }, refetchQueries: ['POSTS'] }) } } From 4ae1c6a25f980568cb9fc2395e0eef18e82ccec3 Mon Sep 17 00:00:00 2001 From: David Price Date: Mon, 13 Apr 2020 15:47:02 -0700 Subject: [PATCH 31/55] revert matrix build setup --- .github/workflows/build-eslint-jest.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-eslint-jest.yaml b/.github/workflows/build-eslint-jest.yaml index 89cb69692a0b..7b2c75519fd9 100644 --- a/.github/workflows/build-eslint-jest.yaml +++ b/.github/workflows/build-eslint-jest.yaml @@ -8,13 +8,12 @@ on: jobs: build: - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest strategy: matrix: - os: [ubuntu-latest] node-version: ['12'] fail-fast: false - name: ${{ matrix.os }} | Node ${{ matrix.node-version }} latest + name: ${{ runs-on }} | Node ${{ matrix.node-version }} latest steps: - uses: actions/checkout@v2 - name: Setup node From 8a4a6380117426cd04ad6e84316864ae1c6039bb Mon Sep 17 00:00:00 2001 From: David Price Date: Mon, 13 Apr 2020 15:48:31 -0700 Subject: [PATCH 32/55] remove runs-on var due to error --- .github/workflows/build-eslint-jest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-eslint-jest.yaml b/.github/workflows/build-eslint-jest.yaml index 7b2c75519fd9..445074b666dc 100644 --- a/.github/workflows/build-eslint-jest.yaml +++ b/.github/workflows/build-eslint-jest.yaml @@ -13,7 +13,7 @@ jobs: matrix: node-version: ['12'] fail-fast: false - name: ${{ runs-on }} | Node ${{ matrix.node-version }} latest + name: Ubuntu latest | Node ${{ matrix.node-version }} latest steps: - uses: actions/checkout@v2 - name: Setup node From b9d0751275659c5b7e18e9975445a12cac791968 Mon Sep 17 00:00:00 2001 From: David S Price Date: Mon, 13 Apr 2020 16:01:28 -0700 Subject: [PATCH 33/55] v0.5.0-rc.1 --- lerna.json | 2 +- packages/api/package.json | 4 ++-- packages/cli/package.json | 4 ++-- packages/core/package.json | 10 +++++----- packages/create-redwood-app/package.json | 4 ++-- packages/dev-server/package.json | 4 ++-- packages/eslint-config/package.json | 4 ++-- packages/eslint-plugin-redwood/package.json | 2 +- packages/internal/package.json | 2 +- packages/router/package.json | 2 +- packages/web/package.json | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index f5e121064a11..1b622862343b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.4.0", + "version": "0.5.0-rc.1", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/api/package.json b/packages/api/package.json index ff2cdbb87bda..6df63f0eafb3 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/api", - "version": "0.4.0", + "version": "0.5.0-rc.1", "files": [ "dist", "importAll.macro.js" @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@prisma/client": "2.0.0-beta.2", - "@redwoodjs/internal": "^0.4.0", + "@redwoodjs/internal": "^0.5.0-rc.1", "apollo-server-lambda": "2.11.0", "babel-plugin-macros": "^2.8.0", "core-js": "3.6.4", diff --git a/packages/cli/package.json b/packages/cli/package.json index b83d0d2187a5..0bb069d12e63 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@redwoodjs/cli", "description": "The Redwood Command Line", - "version": "0.4.0", + "version": "0.5.0-rc.1", "license": "MIT", "bin": { "redwood": "./dist/index.js", @@ -12,7 +12,7 @@ ], "dependencies": { "@prisma/sdk": "^2.0.0-beta.2", - "@redwoodjs/internal": "^0.4.0", + "@redwoodjs/internal": "^0.5.0-rc.1", "camelcase": "^5.3.1", "chalk": "^3.0.0", "concurrently": "^5.1.0", diff --git a/packages/core/package.json b/packages/core/package.json index 2fee50a3b612..e6ef47fa5708 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/core", - "version": "0.4.0", + "version": "0.5.0-rc.1", "license": "MIT", "files": [ "config", @@ -17,10 +17,10 @@ "@babel/preset-typescript": "^7.9.0", "@babel/runtime-corejs3": "^7.9.2", "@prisma/cli": "2.0.0-beta.2", - "@redwoodjs/cli": "^0.4.0", - "@redwoodjs/dev-server": "^0.4.0", - "@redwoodjs/eslint-config": "^0.4.0", - "@redwoodjs/internal": "^0.4.0", + "@redwoodjs/cli": "^0.5.0-rc.1", + "@redwoodjs/dev-server": "^0.5.0-rc.1", + "@redwoodjs/eslint-config": "^0.5.0-rc.1", + "@redwoodjs/internal": "^0.5.0-rc.1", "@testing-library/jest-dom": "^5.3.0", "@testing-library/react": "^10.0.1", "@types/jest": "^25.1.4", diff --git a/packages/create-redwood-app/package.json b/packages/create-redwood-app/package.json index f5c06ec6ffd7..6b165afc76b8 100644 --- a/packages/create-redwood-app/package.json +++ b/packages/create-redwood-app/package.json @@ -1,6 +1,6 @@ { "name": "create-redwood-app", - "version": "0.4.0", + "version": "0.5.0-rc.1", "license": "MIT", "bin": "./dist/create-redwood-app.js", "files": [ @@ -8,7 +8,7 @@ ], "dependencies": { "@babel/runtime-corejs3": "^7.9.2", - "@redwoodjs/internal": "^0.4.0", + "@redwoodjs/internal": "^0.5.0-rc.1", "axios": "^0.19.2", "chalk": "^3.0.0", "check-node-version": "^4.0.2", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 342402297871..1e5cc399215d 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/dev-server", - "version": "0.4.0", + "version": "0.5.0-rc.1", "bin": { "dev-server": "./dist/main.js" }, @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@babel/register": "^7.9.0", - "@redwoodjs/internal": "^0.4.0", + "@redwoodjs/internal": "^0.5.0-rc.1", "args": "^5.0.1", "body-parser": "^1.19.0", "chokidar": "^3.3.1", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 8819f05d758f..f6c1b23a2019 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,10 +1,10 @@ { "name": "@redwoodjs/eslint-config", - "version": "0.4.0", + "version": "0.5.0-rc.1", "main": "index.js", "license": "MIT", "dependencies": { - "@redwoodjs/eslint-plugin-redwood": "^0.4.0", + "@redwoodjs/eslint-plugin-redwood": "^0.5.0-rc.1", "@typescript-eslint/eslint-plugin": "^2.25.0", "@typescript-eslint/parser": "^2.25.0", "babel-eslint": "^10.1.0", diff --git a/packages/eslint-plugin-redwood/package.json b/packages/eslint-plugin-redwood/package.json index 6386246edb60..7176d894b67b 100644 --- a/packages/eslint-plugin-redwood/package.json +++ b/packages/eslint-plugin-redwood/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/eslint-plugin-redwood", - "version": "0.4.0", + "version": "0.5.0-rc.1", "description": "eslint plugin for Redwood rules.", "files": [ "dist" diff --git a/packages/internal/package.json b/packages/internal/package.json index 0e4116717062..dde74b88f6fc 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/internal", - "version": "0.4.0", + "version": "0.5.0-rc.1", "main": "dist/main.js", "files": [ "dist" diff --git a/packages/router/package.json b/packages/router/package.json index 55970a000fab..449659f9af7c 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/router", - "version": "0.4.0", + "version": "0.5.0-rc.1", "files": [ "dist" ], diff --git a/packages/web/package.json b/packages/web/package.json index 4b281d66104e..4a5adf02ad2e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/web", - "version": "0.4.0", + "version": "0.5.0-rc.1", "files": [ "dist" ], From 743a48c048c48d548b7dfccbbc263bc4f23776d4 Mon Sep 17 00:00:00 2001 From: David S Price Date: Mon, 13 Apr 2020 18:40:13 -0700 Subject: [PATCH 34/55] v0.5.0-rc.2 --- lerna.json | 2 +- packages/api/package.json | 4 ++-- packages/cli/package.json | 5 +++-- packages/core/package.json | 10 +++++----- packages/create-redwood-app/package.json | 4 ++-- packages/dev-server/package.json | 4 ++-- packages/eslint-config/package.json | 4 ++-- packages/eslint-plugin-redwood/package.json | 2 +- packages/internal/package.json | 2 +- packages/router/package.json | 2 +- packages/web/package.json | 2 +- 11 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index 1b622862343b..e763ae6411cf 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/api/package.json b/packages/api/package.json index 6df63f0eafb3..ce50785cae4a 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/api", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "files": [ "dist", "importAll.macro.js" @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@prisma/client": "2.0.0-beta.2", - "@redwoodjs/internal": "^0.5.0-rc.1", + "@redwoodjs/internal": "^0.5.0-rc.2", "apollo-server-lambda": "2.11.0", "babel-plugin-macros": "^2.8.0", "core-js": "3.6.4", diff --git a/packages/cli/package.json b/packages/cli/package.json index 0bb069d12e63..354d8b22d5cb 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@redwoodjs/cli", "description": "The Redwood Command Line", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "license": "MIT", "bin": { "redwood": "./dist/index.js", @@ -12,11 +12,12 @@ ], "dependencies": { "@prisma/sdk": "^2.0.0-beta.2", - "@redwoodjs/internal": "^0.5.0-rc.1", + "@redwoodjs/internal": "^0.5.0-rc.2", "camelcase": "^5.3.1", "chalk": "^3.0.0", "concurrently": "^5.1.0", "core-js": "3.6.4", + "cross-env": "^7.0.2", "dotenv-defaults": "^1.1.1", "envinfo": "^7.5.0", "execa": "^4.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index e6ef47fa5708..2ba5defc16df 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/core", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "license": "MIT", "files": [ "config", @@ -17,10 +17,10 @@ "@babel/preset-typescript": "^7.9.0", "@babel/runtime-corejs3": "^7.9.2", "@prisma/cli": "2.0.0-beta.2", - "@redwoodjs/cli": "^0.5.0-rc.1", - "@redwoodjs/dev-server": "^0.5.0-rc.1", - "@redwoodjs/eslint-config": "^0.5.0-rc.1", - "@redwoodjs/internal": "^0.5.0-rc.1", + "@redwoodjs/cli": "^0.5.0-rc.2", + "@redwoodjs/dev-server": "^0.5.0-rc.2", + "@redwoodjs/eslint-config": "^0.5.0-rc.2", + "@redwoodjs/internal": "^0.5.0-rc.2", "@testing-library/jest-dom": "^5.3.0", "@testing-library/react": "^10.0.1", "@types/jest": "^25.1.4", diff --git a/packages/create-redwood-app/package.json b/packages/create-redwood-app/package.json index 6b165afc76b8..dac2778e1e38 100644 --- a/packages/create-redwood-app/package.json +++ b/packages/create-redwood-app/package.json @@ -1,6 +1,6 @@ { "name": "create-redwood-app", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "license": "MIT", "bin": "./dist/create-redwood-app.js", "files": [ @@ -8,7 +8,7 @@ ], "dependencies": { "@babel/runtime-corejs3": "^7.9.2", - "@redwoodjs/internal": "^0.5.0-rc.1", + "@redwoodjs/internal": "^0.5.0-rc.2", "axios": "^0.19.2", "chalk": "^3.0.0", "check-node-version": "^4.0.2", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 1e5cc399215d..82810a105105 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/dev-server", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "bin": { "dev-server": "./dist/main.js" }, @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@babel/register": "^7.9.0", - "@redwoodjs/internal": "^0.5.0-rc.1", + "@redwoodjs/internal": "^0.5.0-rc.2", "args": "^5.0.1", "body-parser": "^1.19.0", "chokidar": "^3.3.1", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index f6c1b23a2019..dd275107827f 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,10 +1,10 @@ { "name": "@redwoodjs/eslint-config", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "main": "index.js", "license": "MIT", "dependencies": { - "@redwoodjs/eslint-plugin-redwood": "^0.5.0-rc.1", + "@redwoodjs/eslint-plugin-redwood": "^0.5.0-rc.2", "@typescript-eslint/eslint-plugin": "^2.25.0", "@typescript-eslint/parser": "^2.25.0", "babel-eslint": "^10.1.0", diff --git a/packages/eslint-plugin-redwood/package.json b/packages/eslint-plugin-redwood/package.json index 7176d894b67b..4d41682aa810 100644 --- a/packages/eslint-plugin-redwood/package.json +++ b/packages/eslint-plugin-redwood/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/eslint-plugin-redwood", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "description": "eslint plugin for Redwood rules.", "files": [ "dist" diff --git a/packages/internal/package.json b/packages/internal/package.json index dde74b88f6fc..4534e2a6ce0e 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/internal", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "main": "dist/main.js", "files": [ "dist" diff --git a/packages/router/package.json b/packages/router/package.json index 449659f9af7c..c865705c46eb 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/router", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "files": [ "dist" ], diff --git a/packages/web/package.json b/packages/web/package.json index 4a5adf02ad2e..33e9b2b1b0c2 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/web", - "version": "0.5.0-rc.1", + "version": "0.5.0-rc.2", "files": [ "dist" ], From 91b3d830e7e5d27f16bb962206bb4718d3a76774 Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Tue, 14 Apr 2020 10:31:39 +0200 Subject: [PATCH 35/55] Add local npm tasks. --- .gitignore | 1 + tasks/publish-local | 26 ++++++++++++++++++++++++++ tasks/run-local-npm | 4 ++++ tasks/verdaccio.yml | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100755 tasks/publish-local create mode 100755 tasks/run-local-npm create mode 100644 tasks/verdaccio.yml diff --git a/.gitignore b/.gitignore index aca15e8a6ee9..1a50e7778b95 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist packages/api/importAll.macro.js lerna-debug.log yarn-error.log +tasks/.verdaccio \ No newline at end of file diff --git a/tasks/publish-local b/tasks/publish-local new file mode 100755 index 000000000000..adbad6f61b00 --- /dev/null +++ b/tasks/publish-local @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +# This script republishes our packages to your local npm +# registry (http://localhost:4873). +# +# Usage: +# Publish a single package: ./tasks/local-publish ./packages/dev-server +# Publish all the packages: ./tasks/local-publish + +if ! lsof -Pi :4873 -sTCP:LISTEN -t >/dev/null; then + echo "Error: Verdaccio is not listening on port 4873, start it with './tasks/run-verdaccio'" + exit 1 +fi + + +if [ -z "$1" ] + then + # Publish all the packages + for d in packages/*/ ; do + ( cd "$d" && npm unpublish --tag dev --registry http://localhost:4873/ --force && npm publish --tag dev --registry http://localhost:4873/ --force ) + done +else + # Publish a single package + ( cd "$1" && npm unpublish --tag dev --registry http://localhost:4873/ --force && npm publish --tag dev --registry http://localhost:4873/ --force ) +fi diff --git a/tasks/run-local-npm b/tasks/run-local-npm new file mode 100755 index 000000000000..1d6589d220ce --- /dev/null +++ b/tasks/run-local-npm @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +verdaccio --config tasks/verdaccio.yml \ No newline at end of file diff --git a/tasks/verdaccio.yml b/tasks/verdaccio.yml new file mode 100644 index 000000000000..c92e57438bed --- /dev/null +++ b/tasks/verdaccio.yml @@ -0,0 +1,37 @@ +# Look here for more config file examples: +# https://github.com/verdaccio/verdaccio/tree/master/conf +storage: .verdaccio +web: + title: Verdaccio +auth: + htpasswd: + file: ./htpasswd +uplinks: + npmjs: + url: https://registry.npmjs.org/ +packages: + '@redwoodjs/*': + access: $all + publish: $all + unpublish: $all + 'create-redwood-app': + access: $all + publish: $all + unpublish: $all + '@*/*': + access: $all + publish: $all + unpublish: $all + proxy: npmjs + '**': + access: $all + publish: $all + unpublish: $all + proxy: npmjs +server: + keepAliveTimeout: 60 +middlewares: + audit: + enabled: true +logs: + - { type: stdout, format: pretty, level: http } \ No newline at end of file From 69dd17afbe5e63715bf775acaebea8f6228d3dd6 Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Tue, 14 Apr 2020 10:31:59 +0200 Subject: [PATCH 36/55] Add build:watch commands to each project. --- package.json | 1 + packages/dev-server/package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d742f8d9695e..46896bc8f937 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ ], "scripts": { "build": "yarn cross-env NODE_ENV=production lerna run build", + "build:watch": "lerna run build:watch --parallel", "test": "lerna run test --stream -- --colors", "lint": "yarn eslint packages", "lint:fix": "yarn eslint --fix packages" diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 7cb0e2182557..47bf06a13de5 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -29,7 +29,8 @@ }, "scripts": { "build": "yarn cross-env NODE_ENV=production babel src -d dist --delete-dir-on-start --extensions \".ts\" --source-maps inline", - "prepublishOnly": "yarn build" + "prepublishOnly": "yarn build", + "build:watch": "nodemon --ignore dist --exec 'yarn build'" }, "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649" } From d65d537d6813b6b1b15a265ba0d063525c7bcbf2 Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Tue, 14 Apr 2020 10:32:28 +0200 Subject: [PATCH 37/55] Add rwdev command. --- packages/cli/package.json | 7 +- packages/cli/src/rwdev.js | 145 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 packages/cli/src/rwdev.js diff --git a/packages/cli/package.json b/packages/cli/package.json index e0e24f196154..996ead920877 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -5,7 +5,8 @@ "license": "MIT", "bin": { "redwood": "./dist/index.js", - "rw": "./dist/index.js" + "rw": "./dist/index.js", + "rwdev": "./dist/rwdev.js" }, "files": [ "dist" @@ -27,11 +28,11 @@ "pascalcase": "^1.0.0", "pluralize": "^8.0.0", "prettier": "^2.0.2", - "rimraf": "^3.0.2", "yargs": "^15.3.1" }, "devDependencies": { - "@types/node-fetch": "^2.5.5" + "@types/node-fetch": "^2.5.5", + "rimraf": "^3.0.2" }, "scripts": { "build": "yarn build:js && yarn build:clean-dist", diff --git a/packages/cli/src/rwdev.js b/packages/cli/src/rwdev.js new file mode 100644 index 000000000000..f00bd306321b --- /dev/null +++ b/packages/cli/src/rwdev.js @@ -0,0 +1,145 @@ +#!/usr/bin/env node +import path from 'path' +import fs from 'fs' + +import yargs from 'yargs' +import { getPaths } from '@redwoodjs/internal' +import execa from 'execa' +import chokidar from 'chokidar' +import _ from 'lodash' + +const RW_BINS = { + redwood: 'cli/dist/index.js', + rw: 'cli/dist/index.js', + rwdev: 'cli/dist/rwdev.js', + 'dev-server': 'dev-server/dist/main.js', +} + +export const resolveFrameworkPath = (RW_PATH) => { + if (!fs.existsSync(RW_PATH)) { + console.error(`Error: '${RW_PATH}' does not exist`) + process.exit(1) + } + return path.resolve(process.cwd(), RW_PATH) +} + +export const fixProjectBinaries = (PROJECT_PATH) => { + Object.keys(RW_BINS) + .map((name) => { + const from = path.join(PROJECT_PATH, 'node_modules/.bin/', name) + const to = path.join( + PROJECT_PATH, + 'node_modules/@redwoodjs', + RW_BINS[name] + ) + console.log(`symlink '${from}' -> '${to}'`) + return [from, to] + }) + .forEach(([from, to]) => { + try { + fs.unlinkSync(from) + } catch (e) { + console.warn(`Warning: Could not unlink ${from}`) + } + try { + fs.symlinkSync(to, from) + } catch (e) { + console.warn(`Warning: Could not symlink ${from} -> ${to}`) + console.log(e) + } + try { + fs.chmodSync(from, '755') + } catch (e) { + console.warn(`Warning: Could not chmod ${from}`) + console.log(e) + } + + try { + fs.chmodSync(from, '755') + } catch (e) { + console.error(`Warning: Could not chmod ${from}`) + console.error(e) + } + }) +} + +// eslint-disable-next-line no-unused-expressions +yargs + .command( + ['watch [RW_PATH]', 'w'], + 'Watch the Redwood Framework path for changes and copy them over to this project', + {}, + ({ RW_PATH = process.env.RW_PATH }) => { + RW_PATH = resolveFrameworkPath(RW_PATH) + + console.log('Redwood Framework Path: ', RW_PATH) + + const src = `${RW_PATH}/packages/` + const dest = `${getPaths().base}/node_modules/@redwoodjs/` + + chokidar + .watch(src, { + persistent: true, + recursive: true, + }) + .on( + 'all', + _.debounce(async (event) => { + // TODO: Figure out if we need to only run based on certain events. + console.log('Trigger event: ', event) + await execa('rsync', ['-rtvu --delete', `'${src}'`, `'${dest}'`], { + shell: true, + stdio: 'inherit', + cleanup: true, + }) + // when rsync is run modify the permission to make binaries executable. + fixProjectBinaries(getPaths().base) + }, 500) + ) + } + ) + .command( + ['install [packageName]', 'i'], + 'Install a package from your local NPM registry', + () => {}, + ({ packageName }) => { + // This command upgrades a Redwood package from the local NPM registry. You + // run the local registry from `./tasks/run-local-npm`. + // See `CONTRIBUTING.md` for more information. + const pkgPath = path.join(getPaths().base, 'node_modules', packageName) + console.log(`Deleting ${pkgPath}`) + try { + fs.rmdirSync(pkgPath, { recursive: true }) + } catch (e) { + console.error(`Error: Could not delete ${pkgPath}`) + process.exit(1) + } + + execa( + 'yarn', + [ + 'upgrade', + `${packageName}@dev`, + '--no-lockfile', + '--registry http://localhost:4873/', + '--check-files', + ], + { + shell: true, + cwd: getPaths().base, + stdio: 'inherit', + extendEnv: true, + cleanup: true, + } + ) + } + ) + .command( + ['fix-bins', 'fix'], + 'Fix Redwood symlinks and permissions', + {}, + () => { + fixProjectBinaries(getPaths().base) + } + ) + .demandCommand().argv From 4ef25ea2e1db926ece73298fee9079db086d6d88 Mon Sep 17 00:00:00 2001 From: Peter Pistorius Date: Tue, 14 Apr 2020 10:32:36 +0200 Subject: [PATCH 38/55] Document development workflows. --- CONTRIBUTING.md | 88 +++++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28f4731db713..ef26bd26df02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,86 +3,94 @@ Before interacting with the Redwood community, please read and understand our [Code of Conduct](https://github.com/redwoodjs/redwood/blob/master/CODE_OF_CONDUCT.md). **Table of Contents** -- [Local Package Development](#Local-Package-Development-Setup) + +- [Local development](#Local-development) +- [Running the Local Server(s)](#Running-the-Local-Server(s)) - [CLI Package Development](#CLI-Package-Development) -## Local Package Development Setup +## Local development -You'll want to run a local redwood app sandbox using your local @redwoodjs packages instead of the current releases from the package registry. To do this we use [`yarn link`](https://classic.yarnpkg.com/en/docs/cli/link/). +When contributing to Redwood you'll often want to add, fix, or change something in the Redwood Framework's monorepo, and see the implementation "running "live" in your own side project or one of our example apps. -### Example Setup: Package `@redwoodjs/cli` Local Dev +We offer two workflows for making that possible: "watch and copy" with some restrictions, and "emulate npm" without restrictions. The consideration for using the "emulate npm" workflow is if you've installed or upgraded a dependency, otherwise the "watch and copy" workflow should be fine. -Assuming you've already cloned `redwoodjs/redwood` locally and run `yarn install` and `yarn build`, navigate to the `packages/cli` directory and run the following command: +### Watch and copy -``` -yarn link +The first step is to watch files for changes and build those changes in the Redwood framework: + +```terminal +cd redwood +yarn build:watch + +@redwoodjs/api: $ nodemon --watch src -e ts,js --ignore dist --exec 'yarn build' +@redwoodjs/core: $ nodemon --ignore dist --exec 'yarn build' +create-redwood-app: $ nodemon --ignore dist --exec 'yarn build' +@redwoodjs/eslint-plugin-redwood: $ nodemon --ignore dist --exec 'yarn build' ``` -You should see a message `success Registered "@redwoodjs/cli"`. +The second step is to watch and copy those changes into your Redwood project: -If you haven't created a local redwood app for testing, first run `yarn create redwood-app [app-name]` and then run `yarn` from the app's root directory. Still in the root directory of the app, run the following: +```terminal +cd example-invoice +yarn rwdev watch ../path/to/redwood -``` -yarn link @redwoodjs/cli +Redwood Framework Path: /Users/peterp/Personal/redwoodjs/redwood +Trigger event: add +building file list ... done ``` -> You can link as many packages as needed at a time. Note: although some packages include others, e.g. /scripts uses /cli as a dependency, you'll still need to link packages individually. +You can also create a `RW_PATH` env var and then you don't have to specify the path in the watch command. -You should see a success message and can confirm the symlink was created by viewing the `/node_modules/@redwoodjs` directory from your editor or via command line `$ ls -l node_modules/@redwoodjs` +Now any changes that are made in the framework are copied into your project. -> HEADS UP: it's easy to forget you're using linked local packages in your sandbox app instead of those published to the package registry. You'll need to manually `git pull` upstream changes to packages. +### Emulating package publishing -### `Yarn Build:Watch` +Sometimes you'll want to test the full development flow from building and publishing our packages, to installing them in your project. We facilitate this using a local NPM registry called [Verdaccio](https://github.com/verdaccio/verdaccio). -As you make changes to a package (in this example `packages/cli`), you'll need to publish locally so the updates are included in your sandbox app. You can manually publish using `yarn build`. But it's more convenient to have the package re-publish each time you make a change. Run the following from the root of the package you're developing, `packages/cli` in this example: +#### Setting up and running a local NPM registry -``` -yarn build:watch +```terminal +yarn add global verdaccio +./tasks/run-local-npm ``` -You'd think you could just go over to your sandbox app and run your `cli` command, like: +This starts Verdaccio (http://localhost:4873) with our configuration file. -``` -yarn redwood generate scaffold MyModel -``` +#### Publishing a package -Unfortunately thanks to a long-standing [issue](https://github.com/yarnpkg/yarn/issues/3587) in Yarn, the bin files that are generated are not executable. You can fix that before running your command like so: +`./tasks/publish-local` will build, unpublish, and publish all the Redwood packages to your local NPM registry with a "dev" tag, for the curious it is the equivalent of running: -``` -chmod +x node_modules/.bin/redwood && yarn redwood generate scaffold MyModel +```terminal +npm unpublish --tag dev --registry http://localhost:4873/ --force +npm publish --tag dev --registry http://localhost:4873/ --force ``` -### Unlinking Packages +You can build a particular package by specifying the path to the package: `./tasks/publish-local ./packages/api`. -Lastly, to reverse the process and remove the links, work backwords using `yarn unlink`. Starting first from the local redwood sandbox app root - -``` -yarn unlink @redwoodjs/cli -yarn install --force -``` +#### Installing published packages -_The latter command reinstalls the current published package._ +Redwood installs `rwdev` a companion CLI development tool that makes installing local npm packages easy: `yarn rwdev install @redwoodjs/dev-server`. -Then from the package directory `/redwoodjs/redwood/packages/cli` of your local clone, run: +This is equivilant to running: -``` -yarn unlink -yarn install --force +```terminal +rm -rf /node_modules/@redwoodjs/dev-server +yarn upgrade @redwoodjs/dev-server@dev --no-lockfile --registry http://localhost:4873/ ``` -### Running the Local Server(s) +## Running the Local Server(s) You can run both the API and Web servers with a single command: -``` +```terminal yarn rw dev ``` However, for local package development, you'll need to manually stop/start the respective server to include changes. In this case you can run the servers for each of the yarn workspaces independently: -``` +```terminal yarn rw dev api yarn rw dev web ``` From 64e86244ccac9e2be860a23026204ccb15a87ad1 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 14 Apr 2020 10:01:18 -0400 Subject: [PATCH 39/55] webpack: include env vars prefixed with REDWOOD_ENV_ --- packages/core/config/webpack.common.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/core/config/webpack.common.js b/packages/core/config/webpack.common.js index bdebd9290c54..2ee68cd4dcbf 100644 --- a/packages/core/config/webpack.common.js +++ b/packages/core/config/webpack.common.js @@ -39,6 +39,14 @@ module.exports = (webpackEnv) => { ] } + const redwoodEnvPrefix = 'REDWOOD_ENV_' + const redwoodEnvKeys = Object.keys(process.env).reduce((prev, next) => { + if (next.startsWith(redwoodEnvPrefix)) { + prev[`process.env.${next}`] = JSON.stringify(process.env[next]) + } + return prev + }, {}) + return { mode: isEnvProduction ? 'production' : 'development', devtool: isEnvProduction ? 'source-map' : 'cheap-module-source-map', @@ -92,6 +100,7 @@ module.exports = (webpackEnv) => { // absolute path of imported file return JSON.stringify(runtimeValue.module.resource) }), + ...redwoodEnvKeys, }), new Dotenv({ path: path.resolve(redwoodPaths.base, '.env'), From 671f23a048504685d0126fec592786d424ca81c6 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 14 Apr 2020 10:13:48 -0400 Subject: [PATCH 40/55] webpack: include env vars matching config includeEnvironmentVariables --- packages/core/config/webpack.common.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/config/webpack.common.js b/packages/core/config/webpack.common.js index 2ee68cd4dcbf..5fc0657ad06e 100644 --- a/packages/core/config/webpack.common.js +++ b/packages/core/config/webpack.common.js @@ -40,8 +40,12 @@ module.exports = (webpackEnv) => { } const redwoodEnvPrefix = 'REDWOOD_ENV_' + const includeEnvKeys = redwoodConfig.web.includeEnvironmentVariables const redwoodEnvKeys = Object.keys(process.env).reduce((prev, next) => { - if (next.startsWith(redwoodEnvPrefix)) { + if ( + next.startsWith(redwoodEnvPrefix) || + (includeEnvKeys && includeEnvKeys.includes(next)) + ) { prev[`process.env.${next}`] = JSON.stringify(process.env[next]) } return prev From 3f963502195ea26438fd75029def3d0ceb8e5eda Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 14 Apr 2020 10:26:33 -0400 Subject: [PATCH 41/55] docs: add includeEnvironmentVariables to redwood.toml.md --- docs/redwood.toml.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/redwood.toml.md b/docs/redwood.toml.md index 228c4683b8a8..4e6d888bd334 100644 --- a/docs/redwood.toml.md +++ b/docs/redwood.toml.md @@ -18,6 +18,10 @@ The port number (integer) to listen to for the web side. TODO +### includeEnvironmentVariables + +The set of environment variable keys (list of strings) to include for the web side, in addition to any that are prefixed with `REDWOOD_ENV_`. + ## [api] This table contains the configuration for api side. From a3f5ec2072f822a441de0e6eaeb4695656d163cc Mon Sep 17 00:00:00 2001 From: Rob Cameron Date: Tue, 14 Apr 2020 16:11:35 -0700 Subject: [PATCH 42/55] Updates GraphQL SDL Required Types logic (#432) * Strict Query return types * Any required fields in schema are required in Input types * Adds separate create and update input types * Use new input types in scaffold new/edit components * Update tests for Create/Update inputs in scaffold Co-authored-by: Rob Cameron --- .../__tests__/fixtures/components/editCell.js | 2 +- .../fixtures/components/foreignKeys/edit.js | 2 +- .../fixtures/components/foreignKeys/new.js | 2 +- .../__tests__/fixtures/components/new.js | 2 +- .../components/EditNameCell.js.template | 2 +- .../templates/components/NewName.js.template | 2 +- .../sdl/__tests__/fixtures/multiWordSdl.js | 9 ++++-- .../__tests__/fixtures/multiWordSdlCrud.js | 17 +++++++---- .../sdl/__tests__/fixtures/singleWordSdl.js | 10 +++++-- .../__tests__/fixtures/singleWordSdlCrud.js | 21 +++++++++---- packages/cli/src/commands/generate/sdl/sdl.js | 30 ++++++++++++++----- .../generate/sdl/templates/sdl.js.template | 18 ++++++----- 12 files changed, 81 insertions(+), 36 deletions(-) diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/editCell.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/editCell.js index b0d0f41e62e0..42b64b74b252 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/editCell.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/editCell.js @@ -16,7 +16,7 @@ export const QUERY = gql` } ` const UPDATE_POST_MUTATION = gql` - mutation UpdatePostMutation($id: Int!, $input: PostInput!) { + mutation UpdatePostMutation($id: Int!, $input: UpdatePostInput!) { updatePost(id: $id, input: $input) { id } diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js index d20e80f660fb..7ac2b6dd7e8f 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js @@ -12,7 +12,7 @@ export const QUERY = gql` } ` const UPDATE_POST_MUTATION = gql` - mutation UpdateUserProfileMutation($id: Int!, $input: UserProfileInput!) { + mutation UpdateUserProfileMutation($id: Int!, $input: UpdateUserProfileInput!) { updateUserProfile(id: $id, input: $input) { id } diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js index 72f982b10096..d54ac1c2cbf2 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js @@ -3,7 +3,7 @@ import { navigate, routes } from '@redwoodjs/router' import UserProfileForm from 'src/components/UserProfileForm' const CREATE_POST_MUTATION = gql` - mutation CreateUserProfileMutation($input: UserProfileInput!) { + mutation CreateUserProfileMutation($input: CreateUserProfileInput!) { createUserProfile(input: $input) { id } diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/new.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/new.js index cc3509c6ccf1..fe67f4560cfa 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/new.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/new.js @@ -3,7 +3,7 @@ import { navigate, routes } from '@redwoodjs/router' import PostForm from 'src/components/PostForm' const CREATE_POST_MUTATION = gql` - mutation CreatePostMutation($input: PostInput!) { + mutation CreatePostMutation($input: CreatePostInput!) { createPost(input: $input) { id } diff --git a/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template index 4c1bb79f4d4a..9632e8ea0b99 100644 --- a/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template +++ b/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template @@ -10,7 +10,7 @@ export const QUERY = gql` } ` const UPDATE_POST_MUTATION = gql` - mutation Update${singularPascalName}Mutation($id: ${idType}!, $input: ${singularPascalName}Input!) { + mutation Update${singularPascalName}Mutation($id: ${idType}!, $input: Update${singularPascalName}Input!) { update${singularPascalName}(id: $id, input: $input) { id } diff --git a/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template index ffe4d0f7a93b..8b4705b770b6 100644 --- a/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template +++ b/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template @@ -3,7 +3,7 @@ import { navigate, routes } from '@redwoodjs/router' import ${singularPascalName}Form from 'src/components/${singularPascalName}Form' const CREATE_POST_MUTATION = gql` - mutation Create${singularPascalName}Mutation($input: ${singularPascalName}Input!) { + mutation Create${singularPascalName}Mutation($input: Create${singularPascalName}Input!) { create${singularPascalName}(input: $input) { id } diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js index 3f11d703b495..7be7ce0aa6ae 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdl.js @@ -7,10 +7,15 @@ export const schema = gql` } type Query { - userProfiles: [UserProfile] + userProfiles: [UserProfile!]! } - input UserProfileInput { + input CreateUserProfileInput { + username: String! + userId: Int! + } + + input UpdateUserProfileInput { username: String userId: Int } diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js index c67569e9a5b8..4daa293cfb90 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/multiWordSdlCrud.js @@ -7,18 +7,23 @@ export const schema = gql` } type Query { - userProfiles: [UserProfile] - userProfile(id: Int!): UserProfile + userProfiles: [UserProfile!]! + userProfile(id: Int!): UserProfile! } - input UserProfileInput { + input CreateUserProfileInput { + username: String! + userId: Int! + } + + input UpdateUserProfileInput { username: String userId: Int } type Mutation { - createUserProfile(input: UserProfileInput!): UserProfile - updateUserProfile(id: Int!, input: UserProfileInput!): UserProfile - deleteUserProfile(id: Int!): UserProfile + createUserProfile(input: CreateUserProfileInput!): UserProfile! + updateUserProfile(id: Int!, input: UpdateUserProfileInput!): UserProfile! + deleteUserProfile(id: Int!): UserProfile! } ` diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js index 9f90ce2314cc..48842d8062ba 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdl.js @@ -8,10 +8,16 @@ export const schema = gql` } type Query { - users: [User] + users: [User!]! } - input UserInput { + input CreateUserInput { + name: String + email: String! + isAdmin: Boolean! + } + + input UpdateUserInput { name: String email: String isAdmin: Boolean diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js index 50321a35c35b..22c00836643d 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/singleWordSdlCrud.js @@ -10,11 +10,20 @@ export const schema = gql` } type Query { - posts: [Post] - post(id: Int!): Post + posts: [Post!]! + post(id: Int!): Post! } - input PostInput { + input CreatePostInput { + title: String! + slug: String! + author: String! + body: String! + image: String + postedAt: DateTime + } + + input UpdatePostInput { title: String slug: String author: String @@ -24,8 +33,8 @@ export const schema = gql` } type Mutation { - createPost(input: PostInput!): Post - updatePost(id: Int!, input: PostInput!): Post - deletePost(id: Int!): Post + createPost(input: CreatePostInput!): Post! + updatePost(id: Int!, input: UpdatePostInput!): Post! + deletePost(id: Int!): Post! } ` diff --git a/packages/cli/src/commands/generate/sdl/sdl.js b/packages/cli/src/commands/generate/sdl/sdl.js index 14393aa0b4e5..262b7e80fac4 100644 --- a/packages/cli/src/commands/generate/sdl/sdl.js +++ b/packages/cli/src/commands/generate/sdl/sdl.js @@ -27,7 +27,7 @@ const querySDL = (model) => { return model.fields.map((field) => modelFieldToSDL(field)) } -const inputSDL = (model, types = {}) => { +const inputSDL = (model, required, types = {}) => { return model.fields .filter((field) => { return ( @@ -35,7 +35,17 @@ const inputSDL = (model, types = {}) => { field.kind !== 'object' ) }) - .map((field) => modelFieldToSDL(field, false, types)) + .map((field) => modelFieldToSDL(field, required, types)) +} + +// creates the CreateInput type (all fields are required) +const createInputSDL = (model, types = {}) => { + return inputSDL(model, true, types) +} + +// creates the UpdateInput type (not all fields are required) +const updateInputSDL = (model, types = {}) => { + return inputSDL(model, false, types) } const idType = (model) => { @@ -64,7 +74,8 @@ const sdlFromSchemaModel = async (name) => { return { query: querySDL(model).join('\n '), - input: inputSDL(model, types).join('\n '), + createInput: createInputSDL(model, types).join('\n '), + updateInput: updateInputSDL(model, types).join('\n '), idType: idType(model), relations: relationsForModel(model), } @@ -76,9 +87,13 @@ const sdlFromSchemaModel = async (name) => { } export const files = async ({ name, crud }) => { - const { query, input, idType, relations } = await sdlFromSchemaModel( - pascalcase(pluralize.singular(name)) - ) + const { + query, + createInput, + updateInput, + idType, + relations, + } = await sdlFromSchemaModel(pascalcase(pluralize.singular(name))) const template = generateTemplate( path.join('sdl', 'templates', 'sdl.js.template'), @@ -86,7 +101,8 @@ export const files = async ({ name, crud }) => { name, crud, query, - input, + createInput, + updateInput, idType, } ) diff --git a/packages/cli/src/commands/generate/sdl/templates/sdl.js.template b/packages/cli/src/commands/generate/sdl/templates/sdl.js.template index a4cc2126984a..c02e7e6ca6da 100644 --- a/packages/cli/src/commands/generate/sdl/templates/sdl.js.template +++ b/packages/cli/src/commands/generate/sdl/templates/sdl.js.template @@ -4,17 +4,21 @@ export const schema = gql` } type Query { - ${pluralCamelName}: [${singularPascalName}]<% if (crud) { %> - ${singularCamelName}(id: ${idType}!): ${singularPascalName}<% } %> + ${pluralCamelName}: [${singularPascalName}!]!<% if (crud) { %> + ${singularCamelName}(id: ${idType}!): ${singularPascalName}!<% } %> } - input ${singularPascalName}Input { - ${input} + input Create${singularPascalName}Input { + ${createInput} + } + + input Update${singularPascalName}Input { + ${updateInput} }<% if (crud) { %> type Mutation { - create${singularPascalName}(input: ${singularPascalName}Input!): ${singularPascalName} - update${singularPascalName}(id: ${idType}!, input: ${singularPascalName}Input!): ${singularPascalName} - delete${singularPascalName}(id: ${idType}!): ${singularPascalName} + create${singularPascalName}(input: Create${singularPascalName}Input!): ${singularPascalName}! + update${singularPascalName}(id: ${idType}!, input: Update${singularPascalName}Input!): ${singularPascalName}! + delete${singularPascalName}(id: ${idType}!): ${singularPascalName}! }<% } %> ` From e0a21587756ce6fcaf6bfcb7982b11943e3e9da8 Mon Sep 17 00:00:00 2001 From: David S Price Date: Tue, 14 Apr 2020 16:37:07 -0700 Subject: [PATCH 43/55] v0.5.0-rc.3 --- lerna.json | 2 +- packages/api/package.json | 4 ++-- packages/cli/package.json | 4 ++-- packages/core/package.json | 10 +++++----- packages/create-redwood-app/package.json | 4 ++-- packages/dev-server/package.json | 4 ++-- packages/eslint-config/package.json | 4 ++-- packages/eslint-plugin-redwood/package.json | 2 +- packages/internal/package.json | 2 +- packages/router/package.json | 2 +- packages/web/package.json | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index e763ae6411cf..18ae2460e3a4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/api/package.json b/packages/api/package.json index ce50785cae4a..496cb9426449 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/api", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "files": [ "dist", "importAll.macro.js" @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@prisma/client": "2.0.0-beta.2", - "@redwoodjs/internal": "^0.5.0-rc.2", + "@redwoodjs/internal": "^0.5.0-rc.3", "apollo-server-lambda": "2.11.0", "babel-plugin-macros": "^2.8.0", "core-js": "3.6.4", diff --git a/packages/cli/package.json b/packages/cli/package.json index 354d8b22d5cb..01a754e1814e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@redwoodjs/cli", "description": "The Redwood Command Line", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "license": "MIT", "bin": { "redwood": "./dist/index.js", @@ -12,7 +12,7 @@ ], "dependencies": { "@prisma/sdk": "^2.0.0-beta.2", - "@redwoodjs/internal": "^0.5.0-rc.2", + "@redwoodjs/internal": "^0.5.0-rc.3", "camelcase": "^5.3.1", "chalk": "^3.0.0", "concurrently": "^5.1.0", diff --git a/packages/core/package.json b/packages/core/package.json index 2ba5defc16df..17f500c9e650 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/core", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "license": "MIT", "files": [ "config", @@ -17,10 +17,10 @@ "@babel/preset-typescript": "^7.9.0", "@babel/runtime-corejs3": "^7.9.2", "@prisma/cli": "2.0.0-beta.2", - "@redwoodjs/cli": "^0.5.0-rc.2", - "@redwoodjs/dev-server": "^0.5.0-rc.2", - "@redwoodjs/eslint-config": "^0.5.0-rc.2", - "@redwoodjs/internal": "^0.5.0-rc.2", + "@redwoodjs/cli": "^0.5.0-rc.3", + "@redwoodjs/dev-server": "^0.5.0-rc.3", + "@redwoodjs/eslint-config": "^0.5.0-rc.3", + "@redwoodjs/internal": "^0.5.0-rc.3", "@testing-library/jest-dom": "^5.3.0", "@testing-library/react": "^10.0.1", "@types/jest": "^25.1.4", diff --git a/packages/create-redwood-app/package.json b/packages/create-redwood-app/package.json index dac2778e1e38..481e5fd6064e 100644 --- a/packages/create-redwood-app/package.json +++ b/packages/create-redwood-app/package.json @@ -1,6 +1,6 @@ { "name": "create-redwood-app", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "license": "MIT", "bin": "./dist/create-redwood-app.js", "files": [ @@ -8,7 +8,7 @@ ], "dependencies": { "@babel/runtime-corejs3": "^7.9.2", - "@redwoodjs/internal": "^0.5.0-rc.2", + "@redwoodjs/internal": "^0.5.0-rc.3", "axios": "^0.19.2", "chalk": "^3.0.0", "check-node-version": "^4.0.2", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 82810a105105..b29020e12f9f 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/dev-server", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "bin": { "dev-server": "./dist/main.js" }, @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@babel/register": "^7.9.0", - "@redwoodjs/internal": "^0.5.0-rc.2", + "@redwoodjs/internal": "^0.5.0-rc.3", "args": "^5.0.1", "body-parser": "^1.19.0", "chokidar": "^3.3.1", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index dd275107827f..471c40013ae2 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,10 +1,10 @@ { "name": "@redwoodjs/eslint-config", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "main": "index.js", "license": "MIT", "dependencies": { - "@redwoodjs/eslint-plugin-redwood": "^0.5.0-rc.2", + "@redwoodjs/eslint-plugin-redwood": "^0.5.0-rc.3", "@typescript-eslint/eslint-plugin": "^2.25.0", "@typescript-eslint/parser": "^2.25.0", "babel-eslint": "^10.1.0", diff --git a/packages/eslint-plugin-redwood/package.json b/packages/eslint-plugin-redwood/package.json index 4d41682aa810..c8c4b4b1dcbc 100644 --- a/packages/eslint-plugin-redwood/package.json +++ b/packages/eslint-plugin-redwood/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/eslint-plugin-redwood", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "description": "eslint plugin for Redwood rules.", "files": [ "dist" diff --git a/packages/internal/package.json b/packages/internal/package.json index 4534e2a6ce0e..0c3e97dcec1d 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/internal", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "main": "dist/main.js", "files": [ "dist" diff --git a/packages/router/package.json b/packages/router/package.json index c865705c46eb..8bac0231c5cd 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/router", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "files": [ "dist" ], diff --git a/packages/web/package.json b/packages/web/package.json index 33e9b2b1b0c2..d700d71fb14f 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/web", - "version": "0.5.0-rc.2", + "version": "0.5.0-rc.3", "files": [ "dist" ], From accd94577dfcf683258def5bef24c3b00201862f Mon Sep 17 00:00:00 2001 From: Rob Cameron Date: Tue, 14 Apr 2020 18:47:01 -0700 Subject: [PATCH 44/55] Fix castInput declaration --- .../__tests__/fixtures/components/foreignKeys/edit.js | 5 ++--- .../__tests__/fixtures/components/foreignKeys/new.js | 5 ++--- .../scaffold/templates/components/EditNameCell.js.template | 5 ++--- .../scaffold/templates/components/NewName.js.template | 5 ++--- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js index 7ac2b6dd7e8f..c43c03a48e63 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/edit.js @@ -29,9 +29,8 @@ export const Success = ({ userProfile }) => { }) const onSave = (input, id) => { - const castInput = input - castInput = Object.assign(castInput, parseInt(input.userId)) - updateUserProfile({ variables: { id, castInput } }) + const castInput = Object.assign(input, { userId: parseInt(input.userId), }) + updateUserProfile({ variables: { id, input: castInput } }) } return ( diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js index d54ac1c2cbf2..41e1f69250db 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js +++ b/packages/cli/src/commands/generate/scaffold/__tests__/fixtures/components/foreignKeys/new.js @@ -18,9 +18,8 @@ const NewUserProfile = () => { }) const onSave = (input) => { - const castInput = input - castInput = Object.assign(castInput, parseInt(input.userId)) - createUserProfile({ variables: { castInput } }) + const castInput = Object.assign(input, { userId: parseInt(input.userId), }) + createUserProfile({ variables: { input: castInput } }) } return ( diff --git a/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template index 9632e8ea0b99..46ac7689876c 100644 --- a/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template +++ b/packages/cli/src/commands/generate/scaffold/templates/components/EditNameCell.js.template @@ -27,9 +27,8 @@ export const Success = ({ ${singularCamelName} }) => { }) const onSave = (input, id) => {<% if (intForeignKeys.length) { %> - const castInput = input - <% intForeignKeys.forEach(key => { %>castInput = Object.assign(castInput, parseInt(input.${key}))<% }) %><% } %> - update${singularPascalName}({ variables: { id, <% if (intForeignKeys.length) { %>castInput<% } else { %>input<% } %> } }) + const castInput = Object.assign(input, { <% intForeignKeys.forEach(key => { %>${key}: parseInt(input.${key}), }<% }) %>)<% } %> + update${singularPascalName}({ variables: { id, <% if (intForeignKeys.length) { %>input: castInput<% } else { %>input<% } %> } }) } return ( diff --git a/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template b/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template index 8b4705b770b6..a7eb9b0bebfa 100644 --- a/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template +++ b/packages/cli/src/commands/generate/scaffold/templates/components/NewName.js.template @@ -18,9 +18,8 @@ const New${singularPascalName} = () => { }) const onSave = (input) => {<% if (intForeignKeys.length) { %> - const castInput = input - <% intForeignKeys.forEach(key => { %>castInput = Object.assign(castInput, parseInt(input.${key}))<% }) %><% } %> - create${singularPascalName}({ variables: { <% if (intForeignKeys.length) { %>castInput<% } else { %>input<% } %> } }) + const castInput = Object.assign(input, { <% intForeignKeys.forEach(key => { %>${key}: parseInt(input.${key}), }<% }) %>)<% } %> + create${singularPascalName}({ variables: { <% if (intForeignKeys.length) { %>input: castInput<% } else { %>input<% } %> } }) } return ( From 937b884ac7bee568d0d9194678867e7aae1cf589 Mon Sep 17 00:00:00 2001 From: David S Price Date: Tue, 14 Apr 2020 19:43:34 -0700 Subject: [PATCH 45/55] v0.5.0 --- lerna.json | 2 +- packages/api/package.json | 4 ++-- packages/cli/package.json | 4 ++-- packages/core/package.json | 10 +++++----- packages/create-redwood-app/package.json | 4 ++-- packages/dev-server/package.json | 4 ++-- packages/eslint-config/package.json | 4 ++-- packages/eslint-plugin-redwood/package.json | 2 +- packages/internal/package.json | 2 +- packages/router/package.json | 2 +- packages/web/package.json | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index 18ae2460e3a4..5c0744dd3640 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.5.0-rc.3", + "version": "0.5.0", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/api/package.json b/packages/api/package.json index 496cb9426449..ca62b6760eeb 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/api", - "version": "0.5.0-rc.3", + "version": "0.5.0", "files": [ "dist", "importAll.macro.js" @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@prisma/client": "2.0.0-beta.2", - "@redwoodjs/internal": "^0.5.0-rc.3", + "@redwoodjs/internal": "^0.5.0", "apollo-server-lambda": "2.11.0", "babel-plugin-macros": "^2.8.0", "core-js": "3.6.4", diff --git a/packages/cli/package.json b/packages/cli/package.json index 01a754e1814e..80549f4164f4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@redwoodjs/cli", "description": "The Redwood Command Line", - "version": "0.5.0-rc.3", + "version": "0.5.0", "license": "MIT", "bin": { "redwood": "./dist/index.js", @@ -12,7 +12,7 @@ ], "dependencies": { "@prisma/sdk": "^2.0.0-beta.2", - "@redwoodjs/internal": "^0.5.0-rc.3", + "@redwoodjs/internal": "^0.5.0", "camelcase": "^5.3.1", "chalk": "^3.0.0", "concurrently": "^5.1.0", diff --git a/packages/core/package.json b/packages/core/package.json index 17f500c9e650..fef8b5916f1d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/core", - "version": "0.5.0-rc.3", + "version": "0.5.0", "license": "MIT", "files": [ "config", @@ -17,10 +17,10 @@ "@babel/preset-typescript": "^7.9.0", "@babel/runtime-corejs3": "^7.9.2", "@prisma/cli": "2.0.0-beta.2", - "@redwoodjs/cli": "^0.5.0-rc.3", - "@redwoodjs/dev-server": "^0.5.0-rc.3", - "@redwoodjs/eslint-config": "^0.5.0-rc.3", - "@redwoodjs/internal": "^0.5.0-rc.3", + "@redwoodjs/cli": "^0.5.0", + "@redwoodjs/dev-server": "^0.5.0", + "@redwoodjs/eslint-config": "^0.5.0", + "@redwoodjs/internal": "^0.5.0", "@testing-library/jest-dom": "^5.3.0", "@testing-library/react": "^10.0.1", "@types/jest": "^25.1.4", diff --git a/packages/create-redwood-app/package.json b/packages/create-redwood-app/package.json index 481e5fd6064e..5f74148e573b 100644 --- a/packages/create-redwood-app/package.json +++ b/packages/create-redwood-app/package.json @@ -1,6 +1,6 @@ { "name": "create-redwood-app", - "version": "0.5.0-rc.3", + "version": "0.5.0", "license": "MIT", "bin": "./dist/create-redwood-app.js", "files": [ @@ -8,7 +8,7 @@ ], "dependencies": { "@babel/runtime-corejs3": "^7.9.2", - "@redwoodjs/internal": "^0.5.0-rc.3", + "@redwoodjs/internal": "^0.5.0", "axios": "^0.19.2", "chalk": "^3.0.0", "check-node-version": "^4.0.2", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index b29020e12f9f..aa662267a728 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/dev-server", - "version": "0.5.0-rc.3", + "version": "0.5.0", "bin": { "dev-server": "./dist/main.js" }, @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@babel/register": "^7.9.0", - "@redwoodjs/internal": "^0.5.0-rc.3", + "@redwoodjs/internal": "^0.5.0", "args": "^5.0.1", "body-parser": "^1.19.0", "chokidar": "^3.3.1", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 471c40013ae2..a7a8250da0c7 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,10 +1,10 @@ { "name": "@redwoodjs/eslint-config", - "version": "0.5.0-rc.3", + "version": "0.5.0", "main": "index.js", "license": "MIT", "dependencies": { - "@redwoodjs/eslint-plugin-redwood": "^0.5.0-rc.3", + "@redwoodjs/eslint-plugin-redwood": "^0.5.0", "@typescript-eslint/eslint-plugin": "^2.25.0", "@typescript-eslint/parser": "^2.25.0", "babel-eslint": "^10.1.0", diff --git a/packages/eslint-plugin-redwood/package.json b/packages/eslint-plugin-redwood/package.json index c8c4b4b1dcbc..dfa78f06ae55 100644 --- a/packages/eslint-plugin-redwood/package.json +++ b/packages/eslint-plugin-redwood/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/eslint-plugin-redwood", - "version": "0.5.0-rc.3", + "version": "0.5.0", "description": "eslint plugin for Redwood rules.", "files": [ "dist" diff --git a/packages/internal/package.json b/packages/internal/package.json index 0c3e97dcec1d..316014795248 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/internal", - "version": "0.5.0-rc.3", + "version": "0.5.0", "main": "dist/main.js", "files": [ "dist" diff --git a/packages/router/package.json b/packages/router/package.json index 8bac0231c5cd..2bf7fd2e188d 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/router", - "version": "0.5.0-rc.3", + "version": "0.5.0", "files": [ "dist" ], diff --git a/packages/web/package.json b/packages/web/package.json index d700d71fb14f..4619a36416f7 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/web", - "version": "0.5.0-rc.3", + "version": "0.5.0", "files": [ "dist" ], From 766fa93490acf05f043a8912fcd901a9c1194d6c Mon Sep 17 00:00:00 2001 From: Rob Cameron Date: Wed, 15 Apr 2020 09:23:35 -0700 Subject: [PATCH 46/55] Change title of Contributing doc for redwoodjs.com --- CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef26bd26df02..bb5698735861 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Setup and Process Overview +# Contributing Before interacting with the Redwood community, please read and understand our [Code of Conduct](https://github.com/redwoodjs/redwood/blob/master/CODE_OF_CONDUCT.md). @@ -96,13 +96,15 @@ yarn rw dev web ``` ## CLI Package Development + We are using [Yargs](https://yargs.js.org/) _Historical note: originally implemented in react-ink (too slow!) then converted._ ### Example + Example dev command: -``` +```javascript export const command = 'dev [app..]' export const desc = 'Run development servers.' export const builder = { From 15da1abcd5bd222d048d098d447fa575dd34134b Mon Sep 17 00:00:00 2001 From: Robert Broersma Date: Sat, 11 Apr 2020 14:38:45 +0200 Subject: [PATCH 47/55] Add useMatch for crafting stateful links --- packages/router/package.json | 3 +- packages/router/src/__tests__/links.test.js | 83 +++++++++++++++++++++ packages/router/src/index.js | 1 + packages/router/src/links.js | 25 +++++-- packages/router/src/location.js | 2 +- 5 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 packages/router/src/__tests__/links.test.js diff --git a/packages/router/package.json b/packages/router/package.json index bbc25fcf4281..572e5ff0de2a 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -21,6 +21,7 @@ }, "gitHead": "2801c132f40263f9fcfbdac8b1750d2e423eb649", "devDependencies": { - "react": "^16.13.1" + "react": "^16.13.1", + "react-dom": "^16.13.1" } } diff --git a/packages/router/src/__tests__/links.test.js b/packages/router/src/__tests__/links.test.js new file mode 100644 index 000000000000..ebbfa06077d9 --- /dev/null +++ b/packages/router/src/__tests__/links.test.js @@ -0,0 +1,83 @@ +import { render } from '@testing-library/react' +// TODO: Remove when jest configs are in place +import { toHaveClass, toHaveStyle } from '@testing-library/jest-dom/matchers' +expect.extend({ toHaveClass, toHaveStyle }) + +import { NavLink, useMatch, Link } from '../links' +import { LocationProvider } from '../location' + +describe('', () => { + it('receives active class on the same path', () => { + const mockLocation = { + pathname: '/dunder-mifflin', + } + + const { getByText } = render( + + + Dunder Mifflin + + + ) + + expect(getByText(/Dunder Mifflin/)).toHaveClass('activeTest') + }) + + it('does NOT receive active class on different path', () => { + const mockLocation = { + pathname: '/staples', + } + + const { getByText } = render( + + + Dunder Mifflin + + + ) + + expect(getByText(/Dunder Mifflin/)).not.toHaveClass('activeTest') + }) +}) + +describe('useMatch', () => { + const MyLink = ({ to, ...rest }) => { + const matchInfo = useMatch(to) + + return ( + + ) + } + + it('returns a match on the same path', () => { + const mockLocation = { + pathname: '/dunder-mifflin', + } + + const { getByText } = render( + + Dunder Mifflin + + ) + + expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: green') + }) + + it('does NOT receive active class on different path', () => { + const mockLocation = { + pathname: '/staples', + } + + const { getByText } = render( + + Dunder Mifflin + + ) + + expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: red') + }) +}) diff --git a/packages/router/src/index.js b/packages/router/src/index.js index f1fd527b794c..2ff3b9437d54 100644 --- a/packages/router/src/index.js +++ b/packages/router/src/index.js @@ -7,6 +7,7 @@ export { Route, Link, NavLink, + useMatch, routes, useParams, navigate, diff --git a/packages/router/src/links.js b/packages/router/src/links.js index b08a3e6c24d6..942e20c83c26 100644 --- a/packages/router/src/links.js +++ b/packages/router/src/links.js @@ -1,6 +1,18 @@ -import { useContext, forwardRef } from 'react' +import { forwardRef, useContext } from 'react' -import { LocationContext, navigate } from './internal' +import { navigate, matchPath, LocationContext } from './internal' + +/** + * Returns true if the URL for the given "route" value matches the current URL. + * This is useful for components that need to know "active" state, e.g. + * . + */ +const useMatch = (route) => { + const location = useContext(LocationContext) + const matchInfo = matchPath(route, location.pathname) + + return matchInfo +} const Link = forwardRef(({ to, ...rest }, ref) => ( ( const NavLink = forwardRef( ({ to, className, activeClassName, ...rest }, ref) => { - const context = useContext(LocationContext) - const theClassName = to === context.pathname ? activeClassName : className + const matchInfo = useMatch(to) + const theClassName = matchInfo.match + ? `${className || ''} ${activeClassName}` + : className + return ( ( ) -export { Location, LocationContext } +export { Location, LocationProvider, LocationContext } From f01618bd05ec8fa34c66082baadced80635a84d2 Mon Sep 17 00:00:00 2001 From: Robert Broersma Date: Wed, 15 Apr 2020 19:29:44 +0200 Subject: [PATCH 48/55] fix(NavLink): remove leading space when className is undefined --- packages/router/src/links.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/router/src/links.js b/packages/router/src/links.js index 942e20c83c26..fde2b14e2875 100644 --- a/packages/router/src/links.js +++ b/packages/router/src/links.js @@ -29,9 +29,9 @@ const Link = forwardRef(({ to, ...rest }, ref) => ( const NavLink = forwardRef( ({ to, className, activeClassName, ...rest }, ref) => { const matchInfo = useMatch(to) - const theClassName = matchInfo.match - ? `${className || ''} ${activeClassName}` - : className + const theClassName = [className, matchInfo.match && activeClassName] + .filter(Boolean) + .join(' ') return ( Date: Wed, 15 Apr 2020 13:24:11 -0700 Subject: [PATCH 49/55] reset GHA step 1 of 2 --- .../{build-eslint-jest.yaml => build-eslint-jest.yaml.temp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{build-eslint-jest.yaml => build-eslint-jest.yaml.temp} (100%) diff --git a/.github/workflows/build-eslint-jest.yaml b/.github/workflows/build-eslint-jest.yaml.temp similarity index 100% rename from .github/workflows/build-eslint-jest.yaml rename to .github/workflows/build-eslint-jest.yaml.temp From 64c063e2e8bba6ff57aca32821f406f9aa87374b Mon Sep 17 00:00:00 2001 From: David S Price Date: Wed, 15 Apr 2020 13:28:00 -0700 Subject: [PATCH 50/55] reset GHA step 2 of 2 --- .../{build-eslint-jest.yaml.temp => build-eslint-jest.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{build-eslint-jest.yaml.temp => build-eslint-jest.yaml} (100%) diff --git a/.github/workflows/build-eslint-jest.yaml.temp b/.github/workflows/build-eslint-jest.yaml similarity index 100% rename from .github/workflows/build-eslint-jest.yaml.temp rename to .github/workflows/build-eslint-jest.yaml From 7ab020e4023c117aed22ef1e6b5e4489cc7a5fb9 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 11 Apr 2020 13:44:39 +0200 Subject: [PATCH 51/55] Adds tests for existing validations & fixes them --- packages/router/src/__tests__/util.test.js | 21 ++++++++++++++++++++- packages/router/src/util.js | 9 +++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/router/src/__tests__/util.test.js b/packages/router/src/__tests__/util.test.js index e2e8413a3212..a21c96068572 100644 --- a/packages/router/src/__tests__/util.test.js +++ b/packages/router/src/__tests__/util.test.js @@ -1,4 +1,4 @@ -import { matchPath, parseSearch } from '../util' +import {matchPath, parseSearch, validatePath} from '../util' describe('matchPath', () => { it('matches paths correctly', () => { @@ -63,6 +63,25 @@ describe('matchPath', () => { }) }) +describe('validatePath', () => { + it.each([ + 'invalid/route', + '{id}/invalid/route', + ' /invalid/route', + ])('rejects "%s" path that does not begin with a slash', (path) => { + expect(validatePath.bind(null, path)).toThrowError(`Route path does not begin with a slash: "${path}"`) + }) + + it.each([ + '/users/{id}/photos/{id}', + '/users/{id}/photos/{id:Int}', + '/users/{id:Int}/photos/{id}', + '/users/{id:Int}/photos/{id:Int}', + ])('rejects path "%s" with duplicate params', (path) => { + expect(validatePath.bind(null, path)).toThrowError(`Route path contains duplicate parameter: "${path}"`) + }) +}) + describe('parseSearch', () => { it('deals silently with an empty search string', () => { expect(parseSearch('')).toEqual({}) diff --git a/packages/router/src/util.js b/packages/router/src/util.js index 2c85344acb84..5cca1da05ec7 100644 --- a/packages/router/src/util.js +++ b/packages/router/src/util.js @@ -128,18 +128,19 @@ const parseSearch = (search) => { const validatePath = (path) => { // Check that path begins with a slash. if (!path.startsWith('/')) { - throw new Error('Route path does not begin with a slash: "' + path + '"') + throw new Error(`Route path does not begin with a slash: "${path}"`) } // Check for duplicate named params. const matches = path.matchAll(/\{([^}]+)\}/g) let memo = {} for (const match of matches) { - const param = match[0] + // Extract the param's name to make sure there aren't any duplicates + const param = match[1].split(':')[0] if (memo[param]) { - throw new Error('Route path contains duplicate parameter: "' + path + '"') + throw new Error(`Route path contains duplicate parameter: "${path}"`) } else { - memo[match[0]] = true + memo[param] = true } } } From 1876b948ba52c5634e71bfe439de850dda11dc8b Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 11 Apr 2020 16:53:13 +0200 Subject: [PATCH 52/55] Reject paths that contain spaces --- packages/router/src/__tests__/util.test.js | 15 +++++++++++++++ packages/router/src/util.js | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/packages/router/src/__tests__/util.test.js b/packages/router/src/__tests__/util.test.js index a21c96068572..dfafebfd4f48 100644 --- a/packages/router/src/__tests__/util.test.js +++ b/packages/router/src/__tests__/util.test.js @@ -72,6 +72,21 @@ describe('validatePath', () => { expect(validatePath.bind(null, path)).toThrowError(`Route path does not begin with a slash: "${path}"`) }) + it.each([ + '/path/to/user profile', + '/path/ to/userprofile', + '/path/to /userprofile', + '/path/to/users/{id: Int}', + '/path/to/users/{id :Int}', + '/path/to/users/{id : Int}', + '/path/to/users/{ id:Int}', + '/path/to/users/{id:Int }', + '/path/to/users/{ id:Int }', + '/path/to/users/{ id : Int }', + ])('rejects paths with spaces: "%s"', (path) => { + expect(validatePath.bind(null, path)).toThrowError(`Route path contains spaces: "${path}"`) + }) + it.each([ '/users/{id}/photos/{id}', '/users/{id}/photos/{id:Int}', diff --git a/packages/router/src/util.js b/packages/router/src/util.js index 5cca1da05ec7..e776af0be1a6 100644 --- a/packages/router/src/util.js +++ b/packages/router/src/util.js @@ -131,6 +131,10 @@ const validatePath = (path) => { throw new Error(`Route path does not begin with a slash: "${path}"`) } + if (path.indexOf(' ') >= 0) { + throw new Error(`Route path contains spaces: "${path}"`) + } + // Check for duplicate named params. const matches = path.matchAll(/\{([^}]+)\}/g) let memo = {} From 898e994d31c1a90510aa635e40574333695abc7c Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 11 Apr 2020 17:19:09 +0200 Subject: [PATCH 53/55] Adds tests for valid paths --- packages/router/src/__tests__/util.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/router/src/__tests__/util.test.js b/packages/router/src/__tests__/util.test.js index dfafebfd4f48..fb1110220524 100644 --- a/packages/router/src/__tests__/util.test.js +++ b/packages/router/src/__tests__/util.test.js @@ -95,6 +95,18 @@ describe('validatePath', () => { ])('rejects path "%s" with duplicate params', (path) => { expect(validatePath.bind(null, path)).toThrowError(`Route path contains duplicate parameter: "${path}"`) }) + + it.each([ + '/users/{id:Int}/photos/{photo_id:Int}', + '/users/{id}/photos/{photo_id}', + '/users/{id}/photos/{photo_id}?format=jpg&w=400&h=400', + '/', + '/404', + '/about', + '/about/redwood', + ])('validates correct path "%s"', (path) => { + expect(validatePath.bind(null, path)).not.toThrow(); + }) }) describe('parseSearch', () => { From a9619cd6bf72ac9de18e773386431ef4e6e261e2 Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 11 Apr 2020 17:19:33 +0200 Subject: [PATCH 54/55] Refactor existing tests using `it.each` to clean things up --- packages/router/src/__tests__/util.test.js | 56 +++++++--------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/packages/router/src/__tests__/util.test.js b/packages/router/src/__tests__/util.test.js index fb1110220524..db8189b34bf8 100644 --- a/packages/router/src/__tests__/util.test.js +++ b/packages/router/src/__tests__/util.test.js @@ -1,53 +1,31 @@ import {matchPath, parseSearch, validatePath} from '../util' describe('matchPath', () => { - it('matches paths correctly', () => { + it.each([ + ['/post/{id:Int}', '/post/notAnInt'], + ['/post/{id:Int}', '/post/2.0'], + ['/post/{id:Int}', '/post/.1'], + ['/post/{id:Int}', '/post/0.1'], + ['/post/{id:Int}', '/post/123abcd'], + ['/post/{id:Int}', '/post/abcd123'], + ['/blog/{year}/{month:Int}/{day}', '/blog/2019/december/07'], + ['/blog/{year}/{month}/{day}', '/blog/2019/07'], + ['/posts/{id}/edit', '/posts//edit'], + ['/about', '/'] + ])('does not match route "%s" with path "%s"', + (route, pathname) => { + expect(matchPath(route, pathname)).toEqual({ match: false }) + }) + + it('matches valid paths and extracts params correctly', () => { expect(matchPath('/post/{id:Int}', '/post/7')).toEqual({ match: true, params: { id: 7 }, }) - expect(matchPath('/post/{id:Int}', '/post/notAnInt')).toEqual({ - match: false, - }) - - expect(matchPath('/post/{id:Int}', '/post/2.0')).toEqual({ - match: false, - }) - - expect(matchPath('/post/{id:Int}', '/post/.1')).toEqual({ - match: false, - }) - - expect(matchPath('/post/{id:Int}', '/post/0.1')).toEqual({ - match: false, - }) - - expect(matchPath('/post/{id:Int}', '/post/123abcd')).toEqual({ - match: false, - }) - - expect(matchPath('/post/{id:Int}', '/post/abcd123')).toEqual({ - match: false, - }) - expect( matchPath('/blog/{year}/{month}/{day}', '/blog/2019/12/07') ).toEqual({ match: true, params: { day: '07', month: '12', year: '2019' } }) - - expect( - matchPath('/blog/{year}/{month:Int}/{day}', '/blog/2019/december/07') - ).toEqual({ match: false }) - - expect(matchPath('/blog/{year}/{month}/{day}', '/blog/2019/07')).toEqual({ - match: false, - }) - - expect(matchPath('/posts/{id}/edit', '/posts//edit')).toEqual({ - match: false, - }) - - expect(matchPath('/about', '/')).toEqual({ match: false }) }) it('transforms a param based on the specified transform', () => { From 06a53842b436d09862d7aeb39d922656cd22d2be Mon Sep 17 00:00:00 2001 From: Olivier Lance Date: Sat, 11 Apr 2020 18:39:55 +0200 Subject: [PATCH 55/55] Makes ESLint happy --- packages/router/src/__tests__/util.test.js | 32 ++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/router/src/__tests__/util.test.js b/packages/router/src/__tests__/util.test.js index db8189b34bf8..adabc33698c6 100644 --- a/packages/router/src/__tests__/util.test.js +++ b/packages/router/src/__tests__/util.test.js @@ -1,4 +1,4 @@ -import {matchPath, parseSearch, validatePath} from '../util' +import { matchPath, parseSearch, validatePath } from '../util' describe('matchPath', () => { it.each([ @@ -11,9 +11,8 @@ describe('matchPath', () => { ['/blog/{year}/{month:Int}/{day}', '/blog/2019/december/07'], ['/blog/{year}/{month}/{day}', '/blog/2019/07'], ['/posts/{id}/edit', '/posts//edit'], - ['/about', '/'] - ])('does not match route "%s" with path "%s"', - (route, pathname) => { + ['/about', '/'], + ])('does not match route "%s" with path "%s"', (route, pathname) => { expect(matchPath(route, pathname)).toEqual({ match: false }) }) @@ -42,13 +41,14 @@ describe('matchPath', () => { }) describe('validatePath', () => { - it.each([ - 'invalid/route', - '{id}/invalid/route', - ' /invalid/route', - ])('rejects "%s" path that does not begin with a slash', (path) => { - expect(validatePath.bind(null, path)).toThrowError(`Route path does not begin with a slash: "${path}"`) - }) + it.each(['invalid/route', '{id}/invalid/route', ' /invalid/route'])( + 'rejects "%s" path that does not begin with a slash', + (path) => { + expect(validatePath.bind(null, path)).toThrowError( + `Route path does not begin with a slash: "${path}"` + ) + } + ) it.each([ '/path/to/user profile', @@ -62,7 +62,9 @@ describe('validatePath', () => { '/path/to/users/{ id:Int }', '/path/to/users/{ id : Int }', ])('rejects paths with spaces: "%s"', (path) => { - expect(validatePath.bind(null, path)).toThrowError(`Route path contains spaces: "${path}"`) + expect(validatePath.bind(null, path)).toThrowError( + `Route path contains spaces: "${path}"` + ) }) it.each([ @@ -71,7 +73,9 @@ describe('validatePath', () => { '/users/{id:Int}/photos/{id}', '/users/{id:Int}/photos/{id:Int}', ])('rejects path "%s" with duplicate params', (path) => { - expect(validatePath.bind(null, path)).toThrowError(`Route path contains duplicate parameter: "${path}"`) + expect(validatePath.bind(null, path)).toThrowError( + `Route path contains duplicate parameter: "${path}"` + ) }) it.each([ @@ -83,7 +87,7 @@ describe('validatePath', () => { '/about', '/about/redwood', ])('validates correct path "%s"', (path) => { - expect(validatePath.bind(null, path)).not.toThrow(); + expect(validatePath.bind(null, path)).not.toThrow() }) })