From e39cbac7f543448e1504866a91d2f4188545f60e Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Tue, 17 Feb 2026 14:48:08 -0600 Subject: [PATCH 1/7] chore: base configs, to move from lap --- tutormfe/hooks.py | 6 ++++++ tutormfe/plugin.py | 26 +++++++++++++++++++++-- tutormfe/templates/mfe/apps/mfe/Caddyfile | 6 ++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tutormfe/hooks.py b/tutormfe/hooks.py index d88c7b44..4526686a 100644 --- a/tutormfe/hooks.py +++ b/tutormfe/hooks.py @@ -11,7 +11,13 @@ from tutor.core.hooks import Filter MFE_ATTRS_TYPE = t.Dict[t.Literal["repository", "port", "version"], t.Union["str", int]] +FRONTEND_TEMPLATE_SITE_ATTRS_TYPE = t.Dict[t.Literal["repository", "version"], t.Union["str", int]] MFE_APPS: Filter[dict[str, MFE_ATTRS_TYPE], []] = Filter() +# This will hold the frontent-template-site like repos (ideally one) +FRONTEND_BASE_APPS: Filter[dict[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE], []] = Filter() +# This will hold the list of which apps are "enabled" so we can switch between mfe and frontend-base ones +FRONTEND_APPS: Filter[dict[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE], []] = Filter() + PLUGIN_SLOTS: Filter[list[tuple[str, str, str]], []] = Filter() diff --git a/tutormfe/plugin.py b/tutormfe/plugin.py index 798f2c72..e83e250e 100644 --- a/tutormfe/plugin.py +++ b/tutormfe/plugin.py @@ -5,7 +5,7 @@ from glob import glob import importlib_resources -from tutor import fmt +from tutor import fmt, config as tutor_config from tutor import hooks as tutor_hooks from tutor.__about__ import __version_suffix__ from tutor.bindmount import iter_mounts @@ -13,7 +13,7 @@ from tutor.types import Config, get_typed from .__about__ import __version__ -from .hooks import MFE_APPS, MFE_ATTRS_TYPE, PLUGIN_SLOTS +from .hooks import MFE_APPS, MFE_ATTRS_TYPE, FRONTEND_BASE_APPS, FRONTEND_APPS, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE, PLUGIN_SLOTS # Handle version suffix in main mode, just like tutor core if __version_suffix__: @@ -95,6 +95,14 @@ def get_mfes() -> dict[str, MFE_ATTRS_TYPE]: return MFE_APPS.apply({}) +@tutor_hooks.lru_cache +def get_frontend_apps() -> dict[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE]: + """ + This function is cached for performance. + """ + return FRONTEND_APPS.apply({}) + + class MFEMountData: """Stores categorized mounted and unmounted MFEs.""" @@ -129,6 +137,14 @@ def iter_mfes() -> t.Iterable[tuple[str, MFE_ATTRS_TYPE]]: """ yield from get_mfes().items() +def iter_frontent_apps() -> t.Iterable[tuple[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE]]: + """ + Yield: + + (name, dict) + """ + yield from get_frontend_apps().items() + def iter_plugin_slots(mfe_name: str) -> t.Iterable[tuple[str, str]]: """ @@ -142,6 +158,9 @@ def iter_plugin_slots(mfe_name: str) -> t.Iterable[tuple[str, str]]: def is_mfe_enabled(mfe_name: str) -> bool: return mfe_name in get_mfes() +def is_frontend_app_enabled(app_name: str) -> bool: + return app_name in get_frontend_apps() + def get_mfe(mfe_name: str) -> t.Union[MFE_ATTRS_TYPE, t.Any]: return get_mfes().get(mfe_name, {}) @@ -152,8 +171,10 @@ def get_mfe(mfe_name: str) -> t.Union[MFE_ATTRS_TYPE, t.Any]: [ ("get_mfe", get_mfe), ("iter_mfes", iter_mfes), + ("iter_frontend_apps", iter_frontent_apps), ("iter_plugin_slots", iter_plugin_slots), ("is_mfe_enabled", is_mfe_enabled), + ("is_frontend_app_enabled", is_frontend_app_enabled), ("MFEMountData", MFEMountData), ] ) @@ -217,6 +238,7 @@ def _mounted_mfe_image_management() -> None: tutor_hooks.Filters.CLI_DO_INIT_TASKS.add_item(("lms", task_file.read())) REPO_PREFIX = "frontend-app-" +FRONTEND_TEMPLATE_SITE_REPO = "frontend-template-site" @tutor_hooks.Filters.COMPOSE_MOUNTS.add() diff --git a/tutormfe/templates/mfe/apps/mfe/Caddyfile b/tutormfe/templates/mfe/apps/mfe/Caddyfile index 115d57c5..906ae059 100644 --- a/tutormfe/templates/mfe/apps/mfe/Caddyfile +++ b/tutormfe/templates/mfe/apps/mfe/Caddyfile @@ -32,7 +32,13 @@ } handle @mfe_{{ app_name }} { uri strip_prefix /{{ app_name }} + {%- if is_frontend_app_enabled(app_name) %} + # {{ app_name }} - using frontend-apps approach + root * /openedx/dist/frontend-template-site + {%- else %} + # {{ app_name }} - using traditional MFE approach root * /openedx/dist/{{ app_name }} + {%- endif %} try_files /{path} /index.html file_server } From 43c6e81ca0b49c554b175db338bc92eb3706ce62 Mon Sep 17 00:00:00 2001 From: Javier Ontiveros Date: Wed, 18 Feb 2026 12:50:05 -0600 Subject: [PATCH 2/7] chore: temp to chanto to mac --- .../mfe-dockerfile-pre-npm-install-template-site | 3 +++ tutormfe/plugin.py | 16 +++++++++++----- tutormfe/templates/mfe/build/mfe/Dockerfile | 3 ++- 3 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 tutormfe/patches/mfe-dockerfile-pre-npm-install-template-site diff --git a/tutormfe/patches/mfe-dockerfile-pre-npm-install-template-site b/tutormfe/patches/mfe-dockerfile-pre-npm-install-template-site new file mode 100644 index 00000000..cd4be137 --- /dev/null +++ b/tutormfe/patches/mfe-dockerfile-pre-npm-install-template-site @@ -0,0 +1,3 @@ +# Copy packs directory before npm install for template-site +# This is needed because template-site has local file dependencies in its package.json +COPY --from=template-site-src /packs /openedx/app/packs \ No newline at end of file diff --git a/tutormfe/plugin.py b/tutormfe/plugin.py index e83e250e..d9b69b8e 100644 --- a/tutormfe/plugin.py +++ b/tutormfe/plugin.py @@ -76,6 +76,11 @@ "repository": "https://github.com/openedx/frontend-app-profile.git", "port": 1995, }, + "template-site": { + "repository": "https://github.com/WGU-Open-edX/frontend-template-site.git", + "port": 8080, + "frontend_base_app": True, # Mark this app as a frontend base app maybe (?) + } } @@ -238,7 +243,8 @@ def _mounted_mfe_image_management() -> None: tutor_hooks.Filters.CLI_DO_INIT_TASKS.add_item(("lms", task_file.read())) REPO_PREFIX = "frontend-app-" -FRONTEND_TEMPLATE_SITE_REPO = "frontend-template-site" +# TODO: for now leave this and then find better semantic namings +FRONTEND_TEMPLATE_SITE_PREFIX = "frontend-" @tutor_hooks.Filters.COMPOSE_MOUNTS.add() @@ -251,12 +257,12 @@ def _mount_frontend_apps( in dev mode, because in production, all MFEs are built and hosted on the singular 'mfe' service container. """ - if path_basename.startswith(REPO_PREFIX): + if path_basename.startswith(REPO_PREFIX) or path_basename.startswith(FRONTEND_TEMPLATE_SITE_PREFIX): # Assumption: # For each repo named frontend-app-APPNAME, there is an associated # docker-compose service named APPNAME. If this assumption is broken, # then Tutor will try to mount the repo in a service that doesn't exist. - app_name = path_basename[len(REPO_PREFIX) :] + app_name = path_basename[len(REPO_PREFIX) :] if path_basename.startswith(REPO_PREFIX) else path_basename[len(FRONTEND_TEMPLATE_SITE_PREFIX) :] volumes += [(app_name, "/openedx/app")] return volumes @@ -266,9 +272,9 @@ def _mount_frontend_apps_on_build( mounts: list[tuple[str, str]], host_path: str ) -> list[tuple[str, str]]: path_basename = os.path.basename(host_path) - if path_basename.startswith(REPO_PREFIX): + if path_basename.startswith(REPO_PREFIX) or path_basename.startswith(FRONTEND_TEMPLATE_SITE_PREFIX): # Bind-mount repo at build-time, both for prod and dev images - app_name = path_basename[len(REPO_PREFIX) :] + app_name = path_basename[len(REPO_PREFIX) :] if path_basename.startswith(REPO_PREFIX) else path_basename[len(FRONTEND_TEMPLATE_SITE_PREFIX) :] mounts.append(("mfe", f"{app_name}-src")) mounts.append((f"{app_name}-dev", f"{app_name}-src")) return mounts diff --git a/tutormfe/templates/mfe/build/mfe/Dockerfile b/tutormfe/templates/mfe/build/mfe/Dockerfile index 7a470822..a503b2fb 100644 --- a/tutormfe/templates/mfe/build/mfe/Dockerfile +++ b/tutormfe/templates/mfe/build/mfe/Dockerfile @@ -50,8 +50,9 @@ RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-install --no-a {{ patch("mfe-dockerfile-post-npm-install-{}".format(app_name)) }} COPY --from={{ app_name }}-src / /openedx/app +{% if app_name != "template-site" %} RUN make OPENEDX_ATLAS_PULL=true ATLAS_OPTIONS="--repository={{ ATLAS_REPOSITORY }} --revision={{ ATLAS_REVISION }} {{ ATLAS_OPTIONS }}" pull_translations - +{% endif %} EXPOSE {{ app['port'] }} # Configuration needed at build time From 76af6877eea7174fafad58e4f5f177d90aefaf53 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Thu, 19 Feb 2026 09:11:46 -0600 Subject: [PATCH 3/7] chore: change computers --- tutormfe/hooks.py | 4 +--- tutormfe/plugin.py | 4 ++-- tutormfe/templates/mfe/apps/mfe/Caddyfile | 18 +++++++++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/tutormfe/hooks.py b/tutormfe/hooks.py index 4526686a..7431439f 100644 --- a/tutormfe/hooks.py +++ b/tutormfe/hooks.py @@ -15,9 +15,7 @@ MFE_APPS: Filter[dict[str, MFE_ATTRS_TYPE], []] = Filter() -# This will hold the frontent-template-site like repos (ideally one) -FRONTEND_BASE_APPS: Filter[dict[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE], []] = Filter() -# This will hold the list of which apps are "enabled" so we can switch between mfe and frontend-base ones +# TODO: This will hold the list of which apps are "enabled" so we can switch between mfe and frontend-base ones FRONTEND_APPS: Filter[dict[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE], []] = Filter() PLUGIN_SLOTS: Filter[list[tuple[str, str, str]], []] = Filter() diff --git a/tutormfe/plugin.py b/tutormfe/plugin.py index d9b69b8e..3e8ac0ea 100644 --- a/tutormfe/plugin.py +++ b/tutormfe/plugin.py @@ -13,7 +13,7 @@ from tutor.types import Config, get_typed from .__about__ import __version__ -from .hooks import MFE_APPS, MFE_ATTRS_TYPE, FRONTEND_BASE_APPS, FRONTEND_APPS, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE, PLUGIN_SLOTS +from .hooks import MFE_APPS, MFE_ATTRS_TYPE, FRONTEND_APPS, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE, PLUGIN_SLOTS # Handle version suffix in main mode, just like tutor core if __version_suffix__: @@ -78,8 +78,8 @@ }, "template-site": { "repository": "https://github.com/WGU-Open-edX/frontend-template-site.git", + "version": "initial", "port": 8080, - "frontend_base_app": True, # Mark this app as a frontend base app maybe (?) } } diff --git a/tutormfe/templates/mfe/apps/mfe/Caddyfile b/tutormfe/templates/mfe/apps/mfe/Caddyfile index 906ae059..6ffa1294 100644 --- a/tutormfe/templates/mfe/apps/mfe/Caddyfile +++ b/tutormfe/templates/mfe/apps/mfe/Caddyfile @@ -1,3 +1,6 @@ +{ + debug +} :8002 { log { output stdout @@ -27,6 +30,7 @@ {% endif %} {% for app_name, app in iter_mfes() %} + {% if app_name != "template-site" %} @mfe_{{ app_name }} { path /{{ app_name }} /{{ app_name }}/* } @@ -34,7 +38,7 @@ uri strip_prefix /{{ app_name }} {%- if is_frontend_app_enabled(app_name) %} # {{ app_name }} - using frontend-apps approach - root * /openedx/dist/frontend-template-site + root * /openedx/dist/template-site {%- else %} # {{ app_name }} - using traditional MFE approach root * /openedx/dist/{{ app_name }} @@ -42,5 +46,17 @@ try_files /{path} /index.html file_server } + {# TODO: Temporal while better rules are in place #} + {% else %} + @mfe_{{ app_name }} { + path / /* + } + handle @mfe_{{ app_name }} { + root * /openedx/dist/template-site + try_files /{path} /index.html + file_server + } + {% endif %} + {% endfor %} } From 7a40e2d22a738009208cfca0ac359711eeae8817 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Thu, 19 Feb 2026 11:44:19 -0600 Subject: [PATCH 4/7] chore: working caddy file (BASE_PATH in site needs /template-site) --- tutormfe/templates/mfe/apps/mfe/Caddyfile | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tutormfe/templates/mfe/apps/mfe/Caddyfile b/tutormfe/templates/mfe/apps/mfe/Caddyfile index 6ffa1294..1630de7d 100644 --- a/tutormfe/templates/mfe/apps/mfe/Caddyfile +++ b/tutormfe/templates/mfe/apps/mfe/Caddyfile @@ -30,7 +30,6 @@ {% endif %} {% for app_name, app in iter_mfes() %} - {% if app_name != "template-site" %} @mfe_{{ app_name }} { path /{{ app_name }} /{{ app_name }}/* } @@ -46,17 +45,6 @@ try_files /{path} /index.html file_server } - {# TODO: Temporal while better rules are in place #} - {% else %} - @mfe_{{ app_name }} { - path / /* - } - handle @mfe_{{ app_name }} { - root * /openedx/dist/template-site - try_files /{path} /index.html - file_server - } - {% endif %} {% endfor %} } From ec75e8a41a711288471075d36e90164e42b797ab Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Thu, 19 Feb 2026 12:52:20 -0600 Subject: [PATCH 5/7] chore: added unique frontend-apps --- tutormfe/hooks.py | 7 ++++--- tutormfe/plugin.py | 21 ++++++++++++++++++--- tutormfe/templates/mfe/apps/mfe/Caddyfile | 2 +- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/tutormfe/hooks.py b/tutormfe/hooks.py index 7431439f..cefe1188 100644 --- a/tutormfe/hooks.py +++ b/tutormfe/hooks.py @@ -11,11 +11,12 @@ from tutor.core.hooks import Filter MFE_ATTRS_TYPE = t.Dict[t.Literal["repository", "port", "version"], t.Union["str", int]] -FRONTEND_TEMPLATE_SITE_ATTRS_TYPE = t.Dict[t.Literal["repository", "version"], t.Union["str", int]] +FRONTEND_TEMPLATE_SITE_ATTRS_TYPE = t.Dict MFE_APPS: Filter[dict[str, MFE_ATTRS_TYPE], []] = Filter() -# TODO: This will hold the list of which apps are "enabled" so we can switch between mfe and frontend-base ones -FRONTEND_APPS: Filter[dict[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE], []] = Filter() +# TODO: This will hold the list of which apps are "enabled" so we can switch between mfe +# and frontend-base ones +FRONTEND_APPS: Filter[dict[str, t.Dict], []] = Filter() PLUGIN_SLOTS: Filter[list[tuple[str, str, str]], []] = Filter() diff --git a/tutormfe/plugin.py b/tutormfe/plugin.py index 3e8ac0ea..443f827a 100644 --- a/tutormfe/plugin.py +++ b/tutormfe/plugin.py @@ -142,13 +142,28 @@ def iter_mfes() -> t.Iterable[tuple[str, MFE_ATTRS_TYPE]]: """ yield from get_mfes().items() -def iter_frontent_apps() -> t.Iterable[tuple[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE]]: +# Iter throgh all mfes and adds the unique frontend apps, +# so we can have a list of all the things that are unique that needs +# to be added to Caddyfile, for example instructor dashboard that was +# created as frontend-base app but didn't exist as a MFE before +# so it returns the whole mfes list plus the unique frontend apps that are not in the mfe list +def iter_frontend_apps() -> t.Iterable[tuple[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE]]: """ Yield: (name, dict) """ - yield from get_frontend_apps().items() + mfes = get_mfes() + frontend_apps = get_frontend_apps() + + # First yield all MFEs + for name, attrs in mfes.items(): + yield (name, attrs) + + # Then yield frontend apps that are not already MFEs + for name, attrs in frontend_apps.items(): + if name not in mfes: + yield (name, attrs) def iter_plugin_slots(mfe_name: str) -> t.Iterable[tuple[str, str]]: @@ -176,7 +191,7 @@ def get_mfe(mfe_name: str) -> t.Union[MFE_ATTRS_TYPE, t.Any]: [ ("get_mfe", get_mfe), ("iter_mfes", iter_mfes), - ("iter_frontend_apps", iter_frontent_apps), + ("iter_frontend_apps", iter_frontend_apps), ("iter_plugin_slots", iter_plugin_slots), ("is_mfe_enabled", is_mfe_enabled), ("is_frontend_app_enabled", is_frontend_app_enabled), diff --git a/tutormfe/templates/mfe/apps/mfe/Caddyfile b/tutormfe/templates/mfe/apps/mfe/Caddyfile index 1630de7d..256081a2 100644 --- a/tutormfe/templates/mfe/apps/mfe/Caddyfile +++ b/tutormfe/templates/mfe/apps/mfe/Caddyfile @@ -29,7 +29,7 @@ redir @authoring /authoring/{re.authoring.1} permanent {% endif %} - {% for app_name, app in iter_mfes() %} + {% for app_name, app in iter_frontend_apps() %} @mfe_{{ app_name }} { path /{{ app_name }} /{{ app_name }}/* } From 4621a8b9f3a6044eb1fd65fb3adfbbaea9636ab3 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Thu, 19 Feb 2026 14:39:57 -0600 Subject: [PATCH 6/7] chore: remote patch that won't be erquired with prepare --- tutormfe/patches/mfe-dockerfile-pre-npm-install-template-site | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 tutormfe/patches/mfe-dockerfile-pre-npm-install-template-site diff --git a/tutormfe/patches/mfe-dockerfile-pre-npm-install-template-site b/tutormfe/patches/mfe-dockerfile-pre-npm-install-template-site deleted file mode 100644 index cd4be137..00000000 --- a/tutormfe/patches/mfe-dockerfile-pre-npm-install-template-site +++ /dev/null @@ -1,3 +0,0 @@ -# Copy packs directory before npm install for template-site -# This is needed because template-site has local file dependencies in its package.json -COPY --from=template-site-src /packs /openedx/app/packs \ No newline at end of file From 31fc159a67dd1c2753d099d564e507ee30eb0242 Mon Sep 17 00:00:00 2001 From: javier ontiveros Date: Thu, 19 Feb 2026 14:51:35 -0600 Subject: [PATCH 7/7] chore: remove debug --- tutormfe/templates/mfe/apps/mfe/Caddyfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/tutormfe/templates/mfe/apps/mfe/Caddyfile b/tutormfe/templates/mfe/apps/mfe/Caddyfile index 256081a2..6aca2bfc 100644 --- a/tutormfe/templates/mfe/apps/mfe/Caddyfile +++ b/tutormfe/templates/mfe/apps/mfe/Caddyfile @@ -1,6 +1,3 @@ -{ - debug -} :8002 { log { output stdout