diff --git a/tubular/scripts/frontend_build.py b/tubular/scripts/frontend_build.py index 179440b3..ddf90ea1 100755 --- a/tubular/scripts/frontend_build.py +++ b/tubular/scripts/frontend_build.py @@ -63,6 +63,7 @@ def frontend_build(common_config_file, env_config_file, app_name, version_file): builder = FrontendBuilder(common_config_file, env_config_file, app_name, version_file) builder.install_requirements() app_config = builder.get_app_config() + builder.copy_js_config_file_to_app_root(app_config, app_name) env_vars = [ f"{k}={ensure_wrapped_in_quotes(v)}" for k, v in app_config.items() diff --git a/tubular/scripts/frontend_utils.py b/tubular/scripts/frontend_utils.py index 1114c140..7b8275e5 100755 --- a/tubular/scripts/frontend_utils.py +++ b/tubular/scripts/frontend_utils.py @@ -5,6 +5,7 @@ import io import json import os +import shutil import subprocess import sys @@ -62,6 +63,7 @@ def install_requirements(self): self.FAIL(1, 'Could not run `npm install` for app {}.'.format(self.app_name)) self.install_requirements_npm_aliases() + self.install_requirements_npm_private() def install_requirements_npm_aliases(self): """ Install npm alias requirements for app to build """ @@ -86,6 +88,22 @@ def install_requirements_npm_aliases(self): aliased_installs, self.app_name )) + def install_requirements_npm_private(self): + """ Install npm private requirements for app to build """ + npm_private = self.get_npm_private_config() + if npm_private: + install_list = ' '.join(npm_private) + install_private_proc = subprocess.Popen( + [f'npm install {install_list}'], + cwd=self.app_name, + shell=True + ) + install_private_proc_return_code = install_private_proc.wait() + if install_private_proc_return_code != 0: + self.FAIL(1, 'Could not run `npm install {}` for app {}.'.format( + install_list, self.app_name + )) + def get_app_config(self): """ Combines the common and environment configs APP_CONFIG data """ app_config = self.common_cfg.get('APP_CONFIG', {}) @@ -102,6 +120,14 @@ def get_npm_aliases_config(self): self.LOG('No npm package aliases defined in config.') return npm_aliases_config + def get_npm_private_config(self): + """ Combines the common and environment configs NPM_PRIVATE packages """ + npm_private_config = self.common_cfg.get('NPM_PRIVATE', []) + npm_private_config.extend(self.env_cfg.get('NPM_PRIVATE', [])) + if not npm_private_config: + self.LOG('No npm private packages defined in config.') + return list(set(npm_private_config)) + def build_app(self, env_vars, fail_msg): """ Builds the app with environment variable.""" proc = subprocess.Popen( @@ -127,6 +153,23 @@ def create_version_file(self): except IOError: self.FAIL(1, 'Could not write to version file for app {}.'.format(self.app_name)) + def copy_js_config_file_to_app_root(self, app_config, app_name): + """ Creates a copy of env.config.js(x) file to the app root """ + source = app_config.get('JS_CONFIG_FILEPATH') + + # Skip if JS_CONFIG_FILEPATH not defined + if not source: + return + + filename = app_config.get('LOCAL_JS_CONFIG_FILENAME', "env.config.js") + destination = f"{app_name}/{filename}" + try: + shutil.copyfile(source, destination) + except FileNotFoundError: + self.FAIL(1, f"Could not find '{source}' for copying for app {app_name}.") + except OSError: + self.FAIL(1, f"Could not copy '{source}' to '{destination}', due to destination not writable.") + class FrontendDeployer: """ Utility class for deploying frontends. """ diff --git a/tubular/tests/example-frontend-config/app.yml b/tubular/tests/example-frontend-config/app.yml index 582da6c9..ccb530a6 100644 --- a/tubular/tests/example-frontend-config/app.yml +++ b/tubular/tests/example-frontend-config/app.yml @@ -33,3 +33,8 @@ APP_CONFIG: # Check None. Should all yield `key='None'`. NONE: !!null NONE_WITH_QUOTES: 'None' # `None` instead of `null` because Python loads this file. + + # This can be overridden for any MFE + LOCAL_JS_CONFIG_FILENAME: "env.config.jsx" + + JS_CONFIG_FILEPATH: "dummy/file/path/env.stage.config.jsx" diff --git a/tubular/tests/example-frontend-config/common.yml b/tubular/tests/example-frontend-config/common.yml index 67f822af..4809a283 100644 --- a/tubular/tests/example-frontend-config/common.yml +++ b/tubular/tests/example-frontend-config/common.yml @@ -5,3 +5,6 @@ APP_CONFIG: # Make sure common values can be overriden by app config. COMMON_OVERRIDE_ME: initial_value + + # Local Config File Name + LOCAL_JS_CONFIG_FILENAME: "env.config.js" diff --git a/tubular/tests/test_frontend_build.py b/tubular/tests/test_frontend_build.py index 392cacbc..07603e5e 100644 --- a/tubular/tests/test_frontend_build.py +++ b/tubular/tests/test_frontend_build.py @@ -18,11 +18,12 @@ class TestFrontendBuildConfigHandling(TestCase): Cursory tests for frontend config parsing + marshalling. """ + @mock.patch('tubular.scripts.frontend_utils.shutil.copyfile') @mock.patch.object(FrontendBuilder, 'create_version_file') @mock.patch.object(FrontendBuilder, 'build_app') @mock.patch.object(FrontendBuilder, 'install_requirements') def test_frontend_build_config_handling( - self, mock_install, mock_build, mock_create_version + self, mock_install, mock_build, mock_create_version, mock_shutil_copyfile ): exit_code = None try: @@ -46,6 +47,7 @@ def test_frontend_build_config_handling( assert mock_build.call_args[0][0] == [ "COMMON_VAR='common_value'", "COMMON_OVERRIDE_ME='overriden_value'", + "LOCAL_JS_CONFIG_FILENAME='env.config.jsx'", "VAR_WITH_SINGLE_QUOTES='The // value!'", "VAR_WITH_DOUBLE_QUOTES='The // value!'", "VAR_WITH_SINGLE_THEN_DOUBLE_QUOTES='The // value!'", @@ -62,5 +64,9 @@ def test_frontend_build_config_handling( "BOOL_FALSE_WITH_QUOTES='False'", "NONE='None'", "NONE_WITH_QUOTES='None'", + "JS_CONFIG_FILEPATH='dummy/file/path/env.stage.config.jsx'", ] assert mock_create_version.call_count == 1 + assert mock_shutil_copyfile.call_count == 1 + # Verify that source is correct and destination is rightly formatted + mock_shutil_copyfile.assert_called_with('dummy/file/path/env.stage.config.jsx', 'coolfrontend/env.config.jsx')