From 936bd9ed34817c3165c9c7fdf8e6bec23bd5457f Mon Sep 17 00:00:00 2001 From: Revanth Kumar Annavarapu <35203638+revanth0212@users.noreply.github.com> Date: Wed, 2 Dec 2020 11:07:13 -0600 Subject: [PATCH] Added feature to pick from multiple sample backends. (#2853) * Initial work * Added magento backend validation. * Updated intercept to fetch backends. * Fetching sample backends while creating a pwa app. * Minor. * Added try catches. * Updated docs. * Minor. * Added node-fetch peer dep. * Minor pretty print stuff. * Added lodash and node-fetch deps. * Updated extension desc. * Updated tests. * Minor. * Updated remaining tests. * Added runEnvValidators.js tests. * Fixed linter issues. * Minor. * Added intercept tests. * Using debug instead of console.error. * Moving backend related code to run after configureWebpack. * Updated the skipped test. * Update ENV error reporting message. * Minor. * Minor. * Updated error snapshot test. * Minor. * Minor. * Added or condition for path variable in tests. * Minor. * Updated tests to use snapshots. * Mock everything, lets get this working. * Removed unecessary mocks. * Updated tests. * Using try catch to avoid prj creations. * Reporting different message if otherBackends is empty. * Updated tests. * Prettier fix. * Added console warning for production deployment. * Updated production launch checklist docs. Co-authored-by: Devagouda <40405790+dpatil-magento@users.noreply.github.com> --- packages/create-pwa/lib/index.js | 31 ++++++- packages/create-pwa/package.json | 2 + .../__snapshots__/intercept.spec.js.snap | 21 +++++ .../__tests__/intercept.spec.js | 75 +++++++++++++++ .../venia-sample-backends/intercept.js | 91 +++++++++++++++++++ .../venia-sample-backends/package.json | 24 +++++ .../venia-sample-language-packs/package.json | 2 +- .../lib/BuildBus/declare-base.js | 47 +++++++++- .../loadEnvironment.spec.js.snap | 6 ++ .../runEnvValidators.spec.js.snap | 7 ++ .../__tests__/createDotEnvFile.spec.js | 25 ++--- .../__tests__/loadEnvironment.spec.js | 66 ++++++++------ .../__tests__/runEnvValidators.spec.js | 62 +++++++++++++ .../lib/Utilities/createDotEnvFile.js | 4 +- .../lib/Utilities/loadEnvironment.js | 6 +- .../lib/Utilities/runEnvValidators.js | 73 +++++++++++++++ packages/pwa-buildpack/lib/Utilities/serve.js | 2 +- .../configureWebpack/configureWebpack.js | 2 +- .../lib/__tests__/cli-create-env-file.spec.js | 50 ++++++---- .../lib/__tests__/cli-load-env.spec.js | 20 ++-- .../pwa-buildpack/lib/cli/create-env-file.js | 18 ++-- packages/pwa-buildpack/lib/cli/load-env.js | 9 +- packages/venia-concept/package.json | 1 + .../src/.storybook/webpack.config.js | 2 +- packages/venia-concept/webpack.config.js | 34 +++---- .../venia-ui/.storybook/webpack.config.js | 2 +- .../production-launch-checklist/index.md | 4 + scripts/monorepo-introduction.js | 7 +- 28 files changed, 586 insertions(+), 107 deletions(-) create mode 100644 packages/extensions/venia-sample-backends/__tests__/__snapshots__/intercept.spec.js.snap create mode 100644 packages/extensions/venia-sample-backends/__tests__/intercept.spec.js create mode 100644 packages/extensions/venia-sample-backends/intercept.js create mode 100644 packages/extensions/venia-sample-backends/package.json create mode 100644 packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/runEnvValidators.spec.js.snap create mode 100644 packages/pwa-buildpack/lib/Utilities/__tests__/runEnvValidators.spec.js create mode 100644 packages/pwa-buildpack/lib/Utilities/runEnvValidators.js diff --git a/packages/create-pwa/lib/index.js b/packages/create-pwa/lib/index.js index c47286f29e..e6e6379378 100644 --- a/packages/create-pwa/lib/index.js +++ b/packages/create-pwa/lib/index.js @@ -1,5 +1,6 @@ const { basename, resolve } = require('path'); const os = require('os'); +const fetch = require('node-fetch'); const changeCase = require('change-case'); const inquirer = require('inquirer'); const execa = require('execa'); @@ -7,11 +8,29 @@ const chalk = require('chalk'); const gitUserInfo = require('git-user-info'); const isInvalidPath = require('is-invalid-path'); const isValidNpmName = require('is-valid-npm-name'); +const { uniqBy } = require('lodash'); + const pkg = require('../package.json'); const { - sampleBackends + sampleBackends: defaultSampleBackends } = require('@magento/pwa-buildpack/lib/cli/create-project'); +const removeDuplicateBackends = backendEnvironments => + uniqBy(backendEnvironments, 'url'); + +const fetchSampleBackends = async () => { + try { + const res = await fetch( + 'https://fvp0esmt8f.execute-api.us-east-1.amazonaws.com/default/getSampleBackends' + ); + const { sampleBackends } = await res.json(); + + return sampleBackends.environments; + } catch { + return []; + } +}; + module.exports = async () => { console.log(chalk.greenBright(`${pkg.name} v${pkg.version}`)); console.log( @@ -20,6 +39,16 @@ module.exports = async () => { const userAgent = process.env.npm_config_user_agent || ''; const isYarn = userAgent.includes('yarn'); + const sampleBackendEnvironments = await fetchSampleBackends(); + const filteredBackendEnvironments = removeDuplicateBackends([ + ...sampleBackendEnvironments, + ...defaultSampleBackends.environments + ]); + const sampleBackends = { + ...defaultSampleBackends, + environments: filteredBackendEnvironments + }; + const questions = [ { name: 'directory', diff --git a/packages/create-pwa/package.json b/packages/create-pwa/package.json index 183a0fd852..12532e07db 100644 --- a/packages/create-pwa/package.json +++ b/packages/create-pwa/package.json @@ -37,6 +37,8 @@ "inquirer": "^6.3.1", "is-invalid-path": "^1.0.2", "is-valid-npm-name": "^0.0.4", + "lodash": "~4.17.11", + "node-fetch": "~2.3.0", "webpack": "^4.29.5" } } diff --git a/packages/extensions/venia-sample-backends/__tests__/__snapshots__/intercept.spec.js.snap b/packages/extensions/venia-sample-backends/__tests__/__snapshots__/intercept.spec.js.snap new file mode 100644 index 0000000000..1b9fd0dab1 --- /dev/null +++ b/packages/extensions/venia-sample-backends/__tests__/__snapshots__/intercept.spec.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should call onFail if backend is inactive 1`] = ` +"https://www.magento-backend-2.3.4.com/ is inactive. Please consider using one of these other backends: + + [{\\"name\\":\\"2.3.3-venia-cloud\\",\\"description\\":\\"Magento 2.3.3 with Venia sample data installed\\",\\"url\\":\\"https://master-7rqtwti-mfwmkrjfqvbjk.us-4.magentosite.cloud/\\"}] +" +`; + +exports[`should call onFail with a different error message if environments is empty 1`] = ` +"https://www.magento-backend-2.3.4.com/ is inactive. Please consider using one of these other backends: + + [{\\"name\\":\\"2.3.3-venia-cloud\\",\\"description\\":\\"Magento 2.3.3 with Venia sample data installed\\",\\"url\\":\\"https://master-7rqtwti-mfwmkrjfqvbjk.us-4.magentosite.cloud/\\"}] +" +`; + +exports[`should log warning message in the console 1`] = ` +" + venia-sample-backends is a Dev only extension, please remove it from the project's package.json before going to production. +" +`; diff --git a/packages/extensions/venia-sample-backends/__tests__/intercept.spec.js b/packages/extensions/venia-sample-backends/__tests__/intercept.spec.js new file mode 100644 index 0000000000..75aeddbcfa --- /dev/null +++ b/packages/extensions/venia-sample-backends/__tests__/intercept.spec.js @@ -0,0 +1,75 @@ +jest.mock('node-fetch'); +const fetch = require('node-fetch'); + +const { validateSampleBackend } = require('../intercept'); + +const env = { + MAGENTO_BACKEND_URL: 'https://www.magento-backend-2.3.4.com/' +}; +const onFail = jest.fn().mockName('onFail'); +const debug = jest.fn().mockName('debug'); + +const args = { env, onFail, debug }; + +beforeAll(() => { + console.warn = jest.fn(); +}); + +test('should not call onFail if backend is active', async () => { + fetch.mockResolvedValueOnce({ ok: true }); + + await validateSampleBackend(args); + + expect(onFail).not.toHaveBeenCalled(); +}); + +test('should call onFail if backend is inactive', async () => { + fetch.mockResolvedValueOnce({ ok: false }).mockResolvedValueOnce({ + json: jest.fn().mockResolvedValue({ + sampleBackends: { + environments: [ + { + name: '2.3.3-venia-cloud', + description: + 'Magento 2.3.3 with Venia sample data installed', + url: + 'https://master-7rqtwti-mfwmkrjfqvbjk.us-4.magentosite.cloud/' + }, + { + name: '2.3.4-venia-cloud', + description: + 'Magento 2.3.4 with Venia sample data installed', + url: 'https://www.magento-backend-2.3.4.com/' + } + ] + } + }) + }); + + await validateSampleBackend(args); + + expect(onFail).toHaveBeenCalled(); + expect(onFail.mock.calls[0][0]).toMatchSnapshot(); +}); + +test('should call onFail with a different error message if environments is empty', async () => { + fetch.mockResolvedValueOnce({ ok: false }).mockResolvedValueOnce({ + json: jest.fn().mockResolvedValue({ + sampleBackends: { + environments: [] + } + }) + }); + + await validateSampleBackend(args); + + expect(onFail).toHaveBeenCalled(); + expect(onFail.mock.calls[0][0]).toMatchSnapshot(); +}); + +test('should log warning message in the console', async () => { + await validateSampleBackend(args); + + expect(console.warn).toHaveBeenCalled(); + expect(console.warn.mock.calls[0][0]).toMatchSnapshot(); +}); diff --git a/packages/extensions/venia-sample-backends/intercept.js b/packages/extensions/venia-sample-backends/intercept.js new file mode 100644 index 0000000000..f5f95e27b0 --- /dev/null +++ b/packages/extensions/venia-sample-backends/intercept.js @@ -0,0 +1,91 @@ +const fetch = require('node-fetch'); + +const isBackendActive = async (env, debug) => { + try { + const magentoBackend = env.MAGENTO_BACKEND_URL; + const res = await fetch(magentoBackend); + + return res.ok; + } catch (err) { + debug(err); + + return false; + } +}; + +const fetchBackends = async debug => { + try { + const res = await fetch( + 'https://fvp0esmt8f.execute-api.us-east-1.amazonaws.com/default/getSampleBackends' + ); + const { sampleBackends } = await res.json(); + + return sampleBackends.environments; + } catch (err) { + debug(err); + + return []; + } +}; + +/** + * Validation function to check if the backend being used is one of the sample backends provided + * by PWA Studio. If yes, the function validates if the backend is active. If not, it reports an + * error by calling the onFail function. In the error being reported, it sends the other sample + * backends that the developers can use. + * + * @param {Object} config.env - The ENV provided to the app, usually avaialable through process.ENV + * @param {Function} config.onFail - callback function to call on validation fail + * @param {Function} config.debug - function to log debug messages in console in debug mode + * + * To watch the debug messages, run the command with DEBUG=*runEnvValidators* + */ +const validateSampleBackend = async config => { + console.warn( + "\n venia-sample-backends is a Dev only extension, please remove it from the project's package.json before going to production. \n" + ); + + const { env, onFail, debug } = config; + + const backendIsActive = await isBackendActive(env, debug); + + if (!backendIsActive) { + debug(`${env.MAGENTO_BACKEND_URL} is inactive`); + + debug('Fetching other backends'); + + const sampleBackends = await fetchBackends(debug); + const otherBackends = sampleBackends.filter( + ({ url }) => url !== env.MAGENTO_BACKEND_URL + ); + + debug('PWA Studio supports the following backends', sampleBackends); + + debug('Reporting backend URL validation failure'); + if (otherBackends.length) { + onFail( + `${ + env.MAGENTO_BACKEND_URL + } is inactive. Please consider using one of these other backends: \n\n ${JSON.stringify( + otherBackends + )} \n` + ); + } else { + onFail( + `${ + env.MAGENTO_BACKEND_URL + } is inactive. Please consider using an active backend \n` + ); + } + } else { + debug(`${env.MAGENTO_BACKEND_URL} is active`); + } +}; + +module.exports = targets => { + targets + .of('@magento/pwa-buildpack') + .validateEnv.tapPromise(validateSampleBackend); +}; + +module.exports.validateSampleBackend = validateSampleBackend; diff --git a/packages/extensions/venia-sample-backends/package.json b/packages/extensions/venia-sample-backends/package.json new file mode 100644 index 0000000000..b823003373 --- /dev/null +++ b/packages/extensions/venia-sample-backends/package.json @@ -0,0 +1,24 @@ +{ + "name": "@magento/venia-sample-backends", + "version": "0.0.1", + "publishConfig": { + "access": "public" + }, + "description": "Provides demo backends and backend validation utils for PWA Studio.", + "main": "./intercept.js", + "scripts": { + "clean": " ", + "test": "jest" + }, + "repository": "github:magento/pwa-studio", + "license": "(OSL-3.0 OR AFL-3.0)", + "peerDependencies": { + "@magento/pwa-buildpack": "~7.0.0", + "node-fetch": "~2.3.0" + }, + "pwa-studio": { + "targets": { + "intercept": "./intercept" + } + } +} diff --git a/packages/extensions/venia-sample-language-packs/package.json b/packages/extensions/venia-sample-language-packs/package.json index 33117cfcc4..a2c9f0dcf3 100644 --- a/packages/extensions/venia-sample-language-packs/package.json +++ b/packages/extensions/venia-sample-language-packs/package.json @@ -20,4 +20,4 @@ "intercept": "./i18n-intercept" } } -} \ No newline at end of file +} diff --git a/packages/pwa-buildpack/lib/BuildBus/declare-base.js b/packages/pwa-buildpack/lib/BuildBus/declare-base.js index 617bd2c33f..76a578e100 100644 --- a/packages/pwa-buildpack/lib/BuildBus/declare-base.js +++ b/packages/pwa-buildpack/lib/BuildBus/declare-base.js @@ -153,7 +153,26 @@ module.exports = targets => { * @member {tapable.AsyncSeriesHook} * @param {transformUpwardIntercept} interceptor */ - transformUpward: new targets.types.AsyncSeries(['definitions']) + transformUpward: new targets.types.AsyncSeries(['definitions']), + + /** + * Collect all ENV validation functions that will run against the + * project's ENV. The functions can be async and they will run in + * parallel. If a validation function wants to stop the whole process + * for instance in case of a serious security issue, it can do so + * by throwing an error. If it wants to report an error, it can do so + * by using the onFail callback provided as an argument. A validation + * function can submit multiple errors by calling the onFail function + * multiple times. All the errors will be queued into an array and + * displayed on the console at the end of the process. + * + * @example + * targets.of('@magento/pwa-buildpack').validateEnv.tapPromise(validateBackendUrl); + * + * @member {tapable.AsyncParallelHook} + * @param {envValidationInterceptor} validator + */ + validateEnv: new targets.types.AsyncParallel(['validator']) }; /** @@ -282,3 +301,29 @@ module.exports = targets => { * @param {object} definition - Parsed UPWARD definition object. * @returns {Promise} */ + +/** Type definitions related to: validateEnv */ + +/** + * Intercept function signature for the validateEnv target. + * + * Interceptors of the `validateEnv` target receive a config object. + * The config object contains the project env, an onFail callback and + * the debug function to be used in case of the debug mode to log more + * inforamtion to the console. + * + * This Target can be used asynchronously in the parallel mode. If a + * validator needs to stop the process immediately, it can throw an error. + * If it needs to report an error but not stop the whole process, it can do + * so by calling the onFail function with the error message it wants to report. + * It can call the onFail multiple times if it wants to report multiple errors. + * + * All the errors will be queued and printed into the console at the end of the + * validation process and the build process will be stopeed. + * + * @callback envValidationInterceptor + * @param {Object} config.env - Project ENV + * @param {Function} config.onFail - On fail callback + * @param {Function} config.debug - Debug function to be used for additional reporting in debug mode + * @returns {Boolean} + */ diff --git a/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/loadEnvironment.spec.js.snap b/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/loadEnvironment.spec.js.snap index 2c0ccbb7f0..e82219833e 100644 --- a/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/loadEnvironment.spec.js.snap +++ b/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/loadEnvironment.spec.js.snap @@ -19,3 +19,9 @@ Array [ ], ] `; + +exports[`throws on load if variable defs are invalid 1`] = ` +"Bad environment variable definition. Section inscrutable variable { + \\"type\\": \\"ineffable\\" +} declares an unknown type ineffable" +`; diff --git a/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/runEnvValidators.spec.js.snap b/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/runEnvValidators.spec.js.snap new file mode 100644 index 0000000000..06b824e36c --- /dev/null +++ b/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/runEnvValidators.spec.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should throw error if there are validation errors reported by interceptors 1`] = ` +"Environment has 2 validation errors: + (1) Danger, + (2) Another error" +`; diff --git a/packages/pwa-buildpack/lib/Utilities/__tests__/createDotEnvFile.spec.js b/packages/pwa-buildpack/lib/Utilities/__tests__/createDotEnvFile.spec.js index 53e922f0cc..d2fd284d9a 100644 --- a/packages/pwa-buildpack/lib/Utilities/__tests__/createDotEnvFile.spec.js +++ b/packages/pwa-buildpack/lib/Utilities/__tests__/createDotEnvFile.spec.js @@ -3,6 +3,7 @@ jest.mock('../../util/pretty-logger', () => ({ error: jest.fn() })); jest.mock('../getEnvVarDefinitions'); +jest.mock('../runEnvValidators', () => jest.fn().mockResolvedValue(true)); const dotenv = require('dotenv'); const getEnvVarDefinitions = require('../getEnvVarDefinitions'); const createDotEnvFile = require('../createDotEnvFile'); @@ -45,47 +46,47 @@ beforeEach(() => { mockLog.error.mockClear(); }); -test('logs errors to default logger if env is not valid', () => { +test('logs errors to default logger if env is not valid', async () => { mockEnvVars.set({ MAGENTO_BACKEND_URL: mockEnvVars.UNSET }); - createDotEnvFile('./'); + await createDotEnvFile('./'); expect(prettyLogger.warn).toHaveBeenCalled(); }); -test('uses alternate logger', () => { +test('uses alternate logger', async () => { mockEnvVars.set({ MAGENTO_BACKEND_URL: mockEnvVars.UNSET }); - createDotEnvFile('./', { logger: mockLog }); + await createDotEnvFile('./', { logger: mockLog }); expect(mockLog.warn).toHaveBeenCalled(); }); -test('returns valid dotenv file if env is valid', () => { +test('returns valid dotenv file if env is valid', async () => { mockEnvVars.set(examples); - const fileText = createDotEnvFile('./', { logger: mockLog }); + const fileText = await createDotEnvFile('./', { logger: mockLog }); expect(snapshotEnvFile(fileText)).toMatchSnapshot(); expect(dotenv.parse(fileText)).toMatchObject(examples); }); -test('populates with examples where available', () => { +test('populates with examples where available', async () => { const unsetExamples = {}; for (const key of Object.keys(examples)) { unsetExamples[key] = mockEnvVars.UNSET; } mockEnvVars.set(unsetExamples); - const fileText = createDotEnvFile('./', { useExamples: true }); + const fileText = await createDotEnvFile('./', { useExamples: true }); expect(dotenv.parse(fileText)).toMatchObject(examples); }); -test('does not print example comment if value is set custom', () => { +test('does not print example comment if value is set custom', async () => { const fakeEnv = { ...examples, MAGENTO_BACKEND_URL: 'https://custom.url', IMAGE_SERVICE_CACHE_EXPIRES: 'a million years' }; mockEnvVars.set(fakeEnv); - const fileText = createDotEnvFile(fakeEnv); + const fileText = await createDotEnvFile(fakeEnv); expect(fileText).not.toMatch(MAGENTO_BACKEND_URL_EXAMPLE); expect(fileText).not.toMatch( `Example: ${examples.IMAGE_SERVICE_CACHE_EXPIRES}` @@ -93,7 +94,7 @@ test('does not print example comment if value is set custom', () => { expect(dotenv.parse(fileText)).not.toMatchObject(examples); }); -test('passing an env object works, but warns deprecation and assumes cwd is context', () => { +test('passing an env object works, but warns deprecation and assumes cwd is context', async () => { getEnvVarDefinitions.mockReturnValue({ sections: [ { @@ -117,7 +118,7 @@ test('passing an env object works, but warns deprecation and assumes cwd is cont }); expect( snapshotEnvFile( - createDotEnvFile({ + await createDotEnvFile({ TEST_ENV_VAR_NOTHING: 'foo' }) ) diff --git a/packages/pwa-buildpack/lib/Utilities/__tests__/loadEnvironment.spec.js b/packages/pwa-buildpack/lib/Utilities/__tests__/loadEnvironment.spec.js index 0a23bb0c94..33af00b375 100644 --- a/packages/pwa-buildpack/lib/Utilities/__tests__/loadEnvironment.spec.js +++ b/packages/pwa-buildpack/lib/Utilities/__tests__/loadEnvironment.spec.js @@ -43,6 +43,10 @@ getEnvVarDefinitions.mockReturnValue({ ] }); +jest.doMock('../runEnvValidators'); +const validateEnv = require('../runEnvValidators'); +validateEnv.mockResolvedValue(true); + jest.mock('../../../package.json', () => { const packageJson = jest.requireActual('../../../package.json'); @@ -69,7 +73,7 @@ afterAll(() => { const loadEnvironment = require('../loadEnvironment'); -test('throws on load if variable defs are invalid', () => { +test('throws on load if variable defs are invalid', async () => { getEnvVarDefinitions.mockReturnValueOnce({ sections: [ { @@ -83,22 +87,21 @@ test('throws on load if variable defs are invalid', () => { ], changes: [] }); - expect(() => loadEnvironment('./')).toThrow( - 'Bad environment variable definition' - ); + + await expect(loadEnvironment('./')).rejects.toThrowErrorMatchingSnapshot(); }); -test('parses dotenv file if argument is path string', () => { +test('parses dotenv file if argument is path string', async () => { dotenv.config.mockReturnValueOnce({ parsed: 'DOTENV PARSED' }); - const { envFilePresent } = loadEnvironment('/path/to/dir'); + const { envFilePresent } = await loadEnvironment('/path/to/dir'); expect(envFilePresent).toBe(true); expect(dotenv.config).toHaveBeenCalledWith({ path: '/path/to/dir/.env' }); }); -test('warns on deprecated api use', () => { - loadEnvironment({ +test('warns on deprecated api use', async () => { + await loadEnvironment({ MUST_BE_BOOLEAN_DUDE: false }); expect(console.warn).toHaveBeenCalledWith( @@ -106,8 +109,8 @@ test('warns on deprecated api use', () => { ); }); -test('does not warn if deprecated API is okay', () => { - loadEnvironment( +test('does not warn if deprecated API is okay', async () => { + await loadEnvironment( { MUST_BE_BOOLEAN_DUDE: false }, @@ -133,9 +136,9 @@ test('does not warn if deprecated API is okay', () => { ); }); -test('debug logs environment in human readable way', () => { +test('debug logs environment in human readable way', async () => { debug.enabled = true; - loadEnvironment({ + await loadEnvironment({ MUST_BE_BOOLEAN_DUDE: false }); expect(debug).toHaveBeenCalledWith( @@ -145,29 +148,29 @@ test('debug logs environment in human readable way', () => { debug.enabled = true; }); -test('sets envFilePresent to false if .env is missing', () => { +test('sets envFilePresent to false if .env is missing', async () => { const enoent = new Error('ENOENT'); enoent.code = 'ENOENT'; dotenv.config.mockReturnValueOnce({ error: enoent }); - const { envFilePresent } = loadEnvironment('/path/to/dir'); + const { envFilePresent } = await loadEnvironment('/path/to/dir'); expect(envFilePresent).toBe(false); }); -test('warns but continues if .env has errors', () => { +test('warns but continues if .env has errors', async () => { dotenv.config.mockReturnValueOnce({ error: new Error('blagh') }); - loadEnvironment('/path/to/dir'); + await loadEnvironment('/path/to/dir'); expect(console.warn).toHaveBeenCalledWith( expect.stringMatching(/could not.*parse/i), expect.any(Error) ); }); -test('emits errors on type mismatch', () => { - const { error } = loadEnvironment({ +test('emits errors on type mismatch', async () => { + const { error } = await loadEnvironment({ MUST_BE_BOOLEAN_DUDE: 'but it aint' }); expect(error.message).toMatch(/MUST_BE_BOOLEAN/); @@ -176,14 +179,19 @@ test('emits errors on type mismatch', () => { ); }); -test('throws anything unexpected from validation', () => { +test('throws anything unexpected from validation', async () => { envalid.cleanEnv.mockImplementationOnce(() => { throw new Error('invalid in a way i cannot even describe'); }); - expect(() => loadEnvironment({})).toThrow('cannot even'); + + try { + await loadEnvironment({}); + } catch (e) { + expect(e.message).toBe('invalid in a way i cannot even describe'); + } }); -test('emits log messages on a custom logger', () => { +test('emits log messages on a custom logger', async () => { const mockLog = { warn: jest.fn().mockName('mockLog.warn'), error: jest.fn().mockName('mockLog.error') @@ -202,7 +210,7 @@ test('emits log messages on a custom logger', () => { ], changes: [] }); - loadEnvironment( + await loadEnvironment( { MUST_BE_BOOLEAN_DUDE: 'twelve' }, @@ -213,7 +221,7 @@ test('emits log messages on a custom logger', () => { ); }); -test('logs all types of change', () => { +test('logs all types of change', async () => { const defs = { sections: [ { @@ -329,7 +337,7 @@ test('logs all types of change', () => { } ] }; - loadEnvironment( + await loadEnvironment( { HAS_BEEN_REMOVED: 'motivation', RON_ARTEST: 'hi', @@ -350,7 +358,7 @@ test('logs all types of change', () => { expect(consoleMessages).toMatchSnapshot(); }); -test('ignores invalid change defs', () => { +test('ignores invalid change defs', async () => { getEnvVarDefinitions.mockReturnValueOnce({ sections: [], changes: [ @@ -363,7 +371,7 @@ test('ignores invalid change defs', () => { expect(() => loadEnvironment({ OH_NOES: 'foo' })).not.toThrow(); }); -test('returns configuration object', () => { +test('returns configuration object', async () => { getEnvVarDefinitions.mockReturnValueOnce({ sections: [ { @@ -401,7 +409,7 @@ test('returns configuration object', () => { GEWGAW_PALADIN: 'level 3', GEWGAW_ROGUE: 'level 4' }; - const config = loadEnvironment({ ...party, NODE_ENV: 'test' }); + const config = await loadEnvironment({ ...party, NODE_ENV: 'test' }); expect(config).toMatchObject({ isProd: false, isProduction: false, @@ -434,7 +442,7 @@ test('returns configuration object', () => { expect(all).not.toHaveProperty('mustang'); }); -test('augments with interceptors of envVarDefinitions target', () => { +test('augments with interceptors of envVarDefinitions target', async () => { getEnvVarDefinitions.mockReset(); getEnvVarDefinitions.mockImplementationOnce(context => jest.requireActual('../getEnvVarDefinitions')(context) @@ -471,7 +479,7 @@ test('augments with interceptors of envVarDefinitions target', () => { ), { virtual: true } ); - loadEnvironment('./other/context'); + await loadEnvironment('./other/context'); expect(console.error).toHaveBeenCalledWith( expect.stringContaining('SIGNAL_INTENSITY') ); diff --git a/packages/pwa-buildpack/lib/Utilities/__tests__/runEnvValidators.spec.js b/packages/pwa-buildpack/lib/Utilities/__tests__/runEnvValidators.spec.js new file mode 100644 index 0000000000..cf1f55a657 --- /dev/null +++ b/packages/pwa-buildpack/lib/Utilities/__tests__/runEnvValidators.spec.js @@ -0,0 +1,62 @@ +jest.mock('../../BuildBus'); +const BuildBus = require('../../BuildBus'); + +const runValidations = require('../runEnvValidators'); + +const context = 'some/valid/path'; +const env = {}; + +test('should run validator targets', async () => { + const initFn = jest.fn(); + const runValidationTargets = jest.fn(); + const getTargetsOfFn = jest.fn().mockReturnValueOnce({ + validateEnv: { promise: runValidationTargets } + }); + const bus = { + init: initFn, + getTargetsOf: getTargetsOfFn + }; + const forFn = jest.fn().mockReturnValue(bus); + const enableTrackingFn = jest.fn(); + BuildBus.for.mockImplementationOnce(forFn); + BuildBus.enableTracking.mockImplementationOnce(enableTrackingFn); + + const returnValue = await runValidations(context, env); + + expect(initFn).toHaveBeenCalled(); + expect(forFn).toHaveBeenCalledWith(context); + expect(getTargetsOfFn).toHaveBeenCalledWith('@magento/pwa-buildpack'); + expect(runValidationTargets).toHaveBeenCalledWith({ + env: expect.any(Object), + onFail: expect.any(Function), + debug: expect.any(Function) + }); + expect(returnValue).toBeTruthy(); +}); + +test('should throw error if there are validation errors reported by interceptors', async () => { + const initFn = jest.fn(); + const runValidationTargets = jest.fn().mockImplementation(({ onFail }) => { + onFail('Danger'); + onFail(new Error('Another error')); + }); + const getTargetsOfFn = jest.fn().mockReturnValueOnce({ + validateEnv: { promise: runValidationTargets } + }); + const bus = { + init: initFn, + getTargetsOf: getTargetsOfFn + }; + const forFn = jest.fn().mockReturnValue(bus); + const enableTrackingFn = jest.fn(); + BuildBus.for.mockImplementationOnce(forFn); + BuildBus.enableTracking.mockImplementationOnce(enableTrackingFn); + + try { + const returnValue = await runValidations(context, env); + + expect(returnValue).toBeUndefined(); + } catch (e) { + expect(e.message).toMatchSnapshot(); + } +}); diff --git a/packages/pwa-buildpack/lib/Utilities/createDotEnvFile.js b/packages/pwa-buildpack/lib/Utilities/createDotEnvFile.js index 1f6008f788..31c0676266 100644 --- a/packages/pwa-buildpack/lib/Utilities/createDotEnvFile.js +++ b/packages/pwa-buildpack/lib/Utilities/createDotEnvFile.js @@ -23,7 +23,7 @@ const graf = txt => }) + '\n'; const paragraphs = (...grafs) => grafs.map(graf).join(blankline); -module.exports = function printEnvFile(dirOrEnv, options = {}) { +module.exports = async function printEnvFile(dirOrEnv, options = {}) { const { logger = prettyLogger, useExamples } = options; // All environment variables Buildpack and PWA Studio use should be defined // in envVarDefinitions.json, along with recent changes to those vars for @@ -37,7 +37,7 @@ module.exports = function printEnvFile(dirOrEnv, options = {}) { } const definitions = getEnvVarDefinitions(context); - const { env, error } = loadEnvironment(dirOrEnv, logger, definitions); + const { env, error } = await loadEnvironment(dirOrEnv, logger, definitions); if (error && !useExamples) { logger.warn( `The current environment is not yet valid; please set any missing variables to build the project before generating a .env file.` diff --git a/packages/pwa-buildpack/lib/Utilities/loadEnvironment.js b/packages/pwa-buildpack/lib/Utilities/loadEnvironment.js index d6f3d0196b..49de625d3b 100644 --- a/packages/pwa-buildpack/lib/Utilities/loadEnvironment.js +++ b/packages/pwa-buildpack/lib/Utilities/loadEnvironment.js @@ -10,6 +10,7 @@ const envalid = require('envalid'); const camelspace = require('camelspace'); const prettyLogger = require('../util/pretty-logger'); const getEnvVarDefinitions = require('./getEnvVarDefinitions'); +const validateEnv = require('./runEnvValidators'); const CompatEnvAdapter = require('./CompatEnvAdapter'); /** @@ -116,7 +117,7 @@ class ProjectConfiguration { * retrieving definitions from the BuildBus. _Internal only._ * @returns {ProjectConfiguration} */ -function loadEnvironment(dirOrEnv, customLogger, providedDefs) { +async function loadEnvironment(dirOrEnv, customLogger, providedDefs) { const logger = customLogger || prettyLogger; let incomingEnv = process.env; let definitions; @@ -207,6 +208,9 @@ This call to loadEnvironment() will assume that the working directory ${context} strict: true } ); + if (typeof dirOrEnv === 'string') { + await validateEnv(dirOrEnv, loadedEnv); + } if (debug.enabled) { // Only do this prettiness if we gotta debug( diff --git a/packages/pwa-buildpack/lib/Utilities/runEnvValidators.js b/packages/pwa-buildpack/lib/Utilities/runEnvValidators.js new file mode 100644 index 0000000000..026f9cd884 --- /dev/null +++ b/packages/pwa-buildpack/lib/Utilities/runEnvValidators.js @@ -0,0 +1,73 @@ +/** + * @module Buildpack/Utilities + */ +const debug = require('debug')('pwa-buildpack:runEnvValidators'); + +/** + * Validate the project ENV. + * Calling this function will invoke the `validateEnv` target of buildpack. All the intercepts + * will be provided the whole process.ENV, a callback function to call if the validation has failed + * and a debug function to be used in case of the debug mode. + * + * @public + * @memberof Buildpack/Utilities + * @param {string} context Project root directory. + * @param {object} env Project ENV. + * @returns {Boolean} + */ +async function validateEnv(context, env) { + debug('Running ENV Validations'); + + const BuildBus = require('../BuildBus'); + + if (process.env.DEBUG && process.env.DEBUG.includes('BuildBus')) { + BuildBus.enableTracking(); + } + + const bus = BuildBus.for(context); + bus.init(); + + const errorMessages = []; + const onFail = errorMessage => errorMessages.push(errorMessage); + + const validationContext = { env, onFail, debug }; + + try { + await bus + .getTargetsOf('@magento/pwa-buildpack') + .validateEnv.promise(validationContext); + } catch { + /** + * While creating a new project using the create-pwa cli + * runEnvValidators will be invoked but the buildpack targets + * will be missing, and it is expected. Hence we are wrapping + * it in a try catch to avoid build failures. Anyways we wont be + * using env validations while creating project. It will be useful + * while building a project. + */ + debug('Buildpack targets not found.'); + } + + if (errorMessages.length) { + debug('Found validation errors in ENV, stopping the build process'); + + const removeErrorPrefix = msg => msg.replace(/^Error:\s*/, ''); + const printValidationMsg = (error, index) => + `\n (${index + 1}) ${removeErrorPrefix(error.message || error)}`; + const prettyErrorList = errorMessages.map(printValidationMsg); + const validationError = new Error( + `Environment has ${ + errorMessages.length + } validation errors: ${prettyErrorList}` + ); + validationError.errorMessages = errorMessages; + + throw validationError; + } + + debug('No issues found in the ENV'); + + return true; +} + +module.exports = validateEnv; diff --git a/packages/pwa-buildpack/lib/Utilities/serve.js b/packages/pwa-buildpack/lib/Utilities/serve.js index 19c1b25120..f065fbb08c 100644 --- a/packages/pwa-buildpack/lib/Utilities/serve.js +++ b/packages/pwa-buildpack/lib/Utilities/serve.js @@ -2,7 +2,7 @@ const loadEnvironment = require('../Utilities/loadEnvironment'); const path = require('path'); module.exports = async function serve(dirname) { - const config = loadEnvironment(dirname); + const config = await loadEnvironment(dirname); if (config.error) { // loadEnvironment takes care of logging it throw new Error('Can not load environment config!'); diff --git a/packages/pwa-buildpack/lib/WebpackTools/configureWebpack/configureWebpack.js b/packages/pwa-buildpack/lib/WebpackTools/configureWebpack/configureWebpack.js index 3bf6a3d1f9..20384d80a1 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/configureWebpack/configureWebpack.js +++ b/packages/pwa-buildpack/lib/WebpackTools/configureWebpack/configureWebpack.js @@ -131,7 +131,7 @@ async function configureWebpack(options) { const babelRootMode = await getBabelRootMode(context); - const projectConfig = loadEnvironment(context); + const projectConfig = await loadEnvironment(context); if (projectConfig.error) { throw projectConfig.error; } diff --git a/packages/pwa-buildpack/lib/__tests__/cli-create-env-file.spec.js b/packages/pwa-buildpack/lib/__tests__/cli-create-env-file.spec.js index a8283407d7..f693641589 100644 --- a/packages/pwa-buildpack/lib/__tests__/cli-create-env-file.spec.js +++ b/packages/pwa-buildpack/lib/__tests__/cli-create-env-file.spec.js @@ -1,15 +1,29 @@ jest.mock('fs'); -const { resolve } = require('path'); + const { writeFileSync } = require('fs'); -const dotenv = require('dotenv'); jest.mock('../Utilities/getEnvVarDefinitions', () => () => require('../../envVarDefinitions.json') ); const createEnvCliBuilder = require('../cli/create-env-file'); +jest.mock('../Utilities/createDotEnvFile', () => + jest.fn().mockResolvedValue('DOT ENV FILE CONTENTS') +); +const createDotEnvFile = require('../Utilities/createDotEnvFile'); + +jest.mock('path', () => { + const path = jest.requireActual('path'); + + return { + ...path, + resolve: jest.fn().mockReturnValue('./pwa-studio/.env') + }; +}); + beforeEach(() => { jest.spyOn(console, 'warn').mockImplementation(() => {}); }); + afterEach(() => { jest.restoreAllMocks(); }); @@ -22,33 +36,37 @@ test('is a yargs builder', () => { }); }); -test('creates and writes file', () => { +test('creates and writes file', async () => { process.env.MAGENTO_BACKEND_URL = 'https://example.com/'; - createEnvCliBuilder.handler({ - directory: process.cwd() + const directory = process.cwd(); + await createEnvCliBuilder.handler({ + directory }); expect(writeFileSync).toHaveBeenCalledWith( - resolve(process.cwd(), '.env'), - expect.stringContaining('https://example.com/'), + './pwa-studio/.env', + 'DOT ENV FILE CONTENTS', 'utf8' ); + expect(createDotEnvFile).toHaveBeenCalledWith(directory, { + useExamples: undefined + }); expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('wrote')); }); -test('creates and writes file with examples', () => { +test('creates and writes file with examples', async () => { process.env.MAGENTO_BACKEND_URL = 'https://example.com/'; - createEnvCliBuilder.handler({ - directory: process.cwd(), + const directory = process.cwd(); + await createEnvCliBuilder.handler({ + directory, useExamples: true }); expect(writeFileSync).toHaveBeenCalledWith( - resolve(process.cwd(), '.env'), - expect.stringContaining('https://example.com/'), + './pwa-studio/.env', + 'DOT ENV FILE CONTENTS', 'utf8' ); - expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('wrote')); - const writtenFile = writeFileSync.mock.calls[0][1]; - expect(dotenv.parse(writtenFile)).toMatchObject({ - MAGENTO_BACKEND_URL: 'https://example.com/' + expect(createDotEnvFile).toHaveBeenCalledWith(directory, { + useExamples: true }); + expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('wrote')); }); diff --git a/packages/pwa-buildpack/lib/__tests__/cli-load-env.spec.js b/packages/pwa-buildpack/lib/__tests__/cli-load-env.spec.js index b2163fc776..2ed44e6eea 100644 --- a/packages/pwa-buildpack/lib/__tests__/cli-load-env.spec.js +++ b/packages/pwa-buildpack/lib/__tests__/cli-load-env.spec.js @@ -7,6 +7,10 @@ const dotenv = require('dotenv'); const loadEnvCliBuilder = require('../cli/load-env'); const createEnv = require('../cli/create-env-file').handler; +jest.mock('../Utilities/runEnvValidators', () => + jest.fn().mockResolvedValue(true) +); + const proc = { exit: jest.fn() }; @@ -39,18 +43,18 @@ test('is a yargs builder', () => { }); }); -test('handler exits nonzero on missing required variables on errors', () => { +test('handler exits nonzero on missing required variables on errors', async () => { // missing required variables dotenv.config.mockReturnValueOnce({ parsed: {} }); - loadEnvCliBuilder.handler({ directory: '.' }, proc); + await loadEnvCliBuilder.handler({ directory: '.' }, proc); expect(console.error).toHaveBeenCalled(); expect(proc.exit).toHaveBeenCalledTimes(1); expect(proc.exit.mock.calls[0][0]).toBeGreaterThan(0); }); -test('handler loads from dotenv file', () => { +test('handler loads from dotenv file', async () => { // Arrange. process.env.MAGENTO_BACKEND_URL = 'https://glorp.zorp'; process.env.CHECKOUT_BRAINTREE_TOKEN = 'my_custom_value'; @@ -59,7 +63,7 @@ test('handler loads from dotenv file', () => { }); // Act. - const result = loadEnvCliBuilder.handler( + const result = await loadEnvCliBuilder.handler( { directory: '.' }, @@ -70,7 +74,7 @@ test('handler loads from dotenv file', () => { expect(result).toBeUndefined(); }); -test('warns if dotenv file does not exist', () => { +test('warns if dotenv file does not exist', async () => { // Arrange. process.env.MAGENTO_BACKEND_URL = 'https://glorp.zorp'; process.env.CHECKOUT_BRAINTREE_TOKEN = 'my_custom_value'; @@ -84,7 +88,7 @@ test('warns if dotenv file does not exist', () => { }); // Act. - loadEnvCliBuilder.handler( + await loadEnvCliBuilder.handler( { directory: '.' }, @@ -97,7 +101,7 @@ test('warns if dotenv file does not exist', () => { ); }); -test('creates a .env file from example values if --core-dev-mode', () => { +test('creates a .env file from example values if --core-dev-mode', async () => { // Arrange. process.env.MAGENTO_BACKEND_URL = 'https://glorp.zorp'; process.env.CHECKOUT_BRAINTREE_TOKEN = 'my_custom_value'; @@ -111,7 +115,7 @@ test('creates a .env file from example values if --core-dev-mode', () => { }); // Act. - loadEnvCliBuilder.handler( + await loadEnvCliBuilder.handler( { directory: '.', coreDevMode: true diff --git a/packages/pwa-buildpack/lib/cli/create-env-file.js b/packages/pwa-buildpack/lib/cli/create-env-file.js index 43c7c11cd4..31d2a95758 100644 --- a/packages/pwa-buildpack/lib/cli/create-env-file.js +++ b/packages/pwa-buildpack/lib/cli/create-env-file.js @@ -1,5 +1,7 @@ const { writeFileSync } = require('fs'); const { resolve } = require('path'); + +const createDotEnvFile = require('../Utilities/createDotEnvFile'); const prettyLogger = require('../util/pretty-logger'); module.exports.command = 'create-env-file '; @@ -13,15 +15,15 @@ module.exports.builder = { } }; -module.exports.handler = function buildpackCli({ directory, useExamples }) { +module.exports.handler = async function buildpackCli({ + directory, + useExamples +}) { const envFilePath = resolve(directory, '.env'); - writeFileSync( - envFilePath, - require('../Utilities/createDotEnvFile')(directory, { - useExamples - }), - 'utf8' - ); + const dotEnvFile = await createDotEnvFile(directory, { + useExamples + }); + writeFileSync(envFilePath, dotEnvFile, 'utf8'); prettyLogger.info( `Successfully wrote a fresh configuration file to ${envFilePath}` ); diff --git a/packages/pwa-buildpack/lib/cli/load-env.js b/packages/pwa-buildpack/lib/cli/load-env.js index e253fe65e4..e60a58f6b0 100644 --- a/packages/pwa-buildpack/lib/cli/load-env.js +++ b/packages/pwa-buildpack/lib/cli/load-env.js @@ -12,13 +12,14 @@ module.exports.builder = { } }; -module.exports.handler = function buildpackCli( +module.exports.handler = async function buildpackCli( { directory, coreDevMode }, proc = process ) { - const { error, envFilePresent } = require('../Utilities/loadEnvironment')( - directory - ); + const { + error, + envFilePresent + } = await require('../Utilities/loadEnvironment')(directory); if (!envFilePresent) { if (coreDevMode) { prettyLogger.warn(`Creating new .env file using example values`); diff --git a/packages/venia-concept/package.json b/packages/venia-concept/package.json index 5b70c915fc..65ad31c11b 100644 --- a/packages/venia-concept/package.json +++ b/packages/venia-concept/package.json @@ -55,6 +55,7 @@ "@magento/pagebuilder": "~3.0.0", "@magento/peregrine": "~8.0.0", "@magento/upward-security-headers": "~1.0.0", + "@magento/venia-sample-backends": "~0.0.1", "@magento/venia-ui": "~5.0.0", "@pmmmwh/react-refresh-webpack-plugin": "0.4.1", "@storybook/react": "~5.2.6", diff --git a/packages/venia-concept/src/.storybook/webpack.config.js b/packages/venia-concept/src/.storybook/webpack.config.js index 02cd641010..48359ecd19 100644 --- a/packages/venia-concept/src/.storybook/webpack.config.js +++ b/packages/venia-concept/src/.storybook/webpack.config.js @@ -15,7 +15,7 @@ const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin' // defines in the docs. // See https://storybook.js.org/docs/configurations/custom-webpack-config/#full-control-mode module.exports = async ({ config: storybookBaseConfig, mode }) => { - const projectConfig = loadEnvironment( + const projectConfig = await loadEnvironment( // Load .env from root path.resolve(__dirname, '../..') ); diff --git a/packages/venia-concept/webpack.config.js b/packages/venia-concept/webpack.config.js index d69f99fd12..ed417da257 100644 --- a/packages/venia-concept/webpack.config.js +++ b/packages/venia-concept/webpack.config.js @@ -11,16 +11,13 @@ const { DefinePlugin } = require('webpack'); const HTMLWebpackPlugin = require('html-webpack-plugin'); module.exports = async env => { - const mediaUrl = await getMediaURL(); - const storeConfigData = await getStoreConfigData(); - const { availableStores } = await getAvailableStoresConfigData(); - - global.MAGENTO_MEDIA_BACKEND_URL = mediaUrl; - global.LOCALE = storeConfigData.locale.replace('_', '-'); - global.AVAILABLE_STORE_VIEWS = availableStores; - - const possibleTypes = await getPossibleTypes(); - + /** + * configureWebpack() returns a regular Webpack configuration object. + * You can customize the build by mutating the object here, as in + * this example. Since it's a regular Webpack configuration, the object + * supports the `module.noParse` option in Webpack, documented here: + * https://webpack.js.org/configuration/module/#modulenoparse + */ const config = await configureWebpack({ context: __dirname, vendor: [ @@ -44,13 +41,16 @@ module.exports = async env => { env }); - /** - * configureWebpack() returns a regular Webpack configuration object. - * You can customize the build by mutating the object here, as in - * this example. Since it's a regular Webpack configuration, the object - * supports the `module.noParse` option in Webpack, documented here: - * https://webpack.js.org/configuration/module/#modulenoparse - */ + const mediaUrl = await getMediaURL(); + const storeConfigData = await getStoreConfigData(); + const { availableStores } = await getAvailableStoresConfigData(); + + global.MAGENTO_MEDIA_BACKEND_URL = mediaUrl; + global.LOCALE = storeConfigData.locale.replace('_', '-'); + global.AVAILABLE_STORE_VIEWS = availableStores; + + const possibleTypes = await getPossibleTypes(); + config.module.noParse = [/braintree\-web\-drop\-in/]; config.plugins = [ ...config.plugins, diff --git a/packages/venia-ui/.storybook/webpack.config.js b/packages/venia-ui/.storybook/webpack.config.js index ae8161662c..c6daa5d97b 100644 --- a/packages/venia-ui/.storybook/webpack.config.js +++ b/packages/venia-ui/.storybook/webpack.config.js @@ -17,7 +17,7 @@ const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin' module.exports = async ({ config: storybookBaseConfig, mode }) => { // The .env for running most of this project comes from venia-concept. // This is not resilient and will need to change if venia-concept is renamed. - const projectConfig = loadEnvironment( + const projectConfig = await loadEnvironment( path.resolve(__dirname, '../../venia-concept') ); diff --git a/pwa-devdocs/src/tutorials/pwa-studio-fundamentals/production-launch-checklist/index.md b/pwa-devdocs/src/tutorials/pwa-studio-fundamentals/production-launch-checklist/index.md index 6105432ed7..91540b29e9 100644 --- a/pwa-devdocs/src/tutorials/pwa-studio-fundamentals/production-launch-checklist/index.md +++ b/pwa-devdocs/src/tutorials/pwa-studio-fundamentals/production-launch-checklist/index.md @@ -46,6 +46,10 @@ yarn start This command starts a production UPWARD-JS server that runs your production build. +## Remove development packages + +Edit your project's `package.json` file and remove any packages you only use in your development environment. For example, the `venia-sample-backends` is an extension included in projects built using the scaffolding tool. + ## Audit with Lighthouse [Lighthouse][] is a web developer tool that audits your website. diff --git a/scripts/monorepo-introduction.js b/scripts/monorepo-introduction.js index 830b7c9053..7138a6fbf4 100644 --- a/scripts/monorepo-introduction.js +++ b/scripts/monorepo-introduction.js @@ -82,9 +82,10 @@ async function prepare() { return () => {}; } }); - const customOrigin = loadEnvironment(veniaPath, nullLogger).section( - 'customOrigin' - ); + const customOrigin = await loadEnvironment( + veniaPath, + nullLogger + ).section('customOrigin'); if (customOrigin.enabled) { const customOriginConfig = await configureHost( Object.assign(customOrigin, {