diff --git a/.gitignore b/.gitignore
index 56854b48cb4..227652838cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,5 +43,4 @@ pip-wheel-metadata
webpack-stats.json
.DS_STORE
CACHE
-.tsconfig-paths.json
-.frontend-configuration-settings.json
+frontend_configuration
diff --git a/arches/app/media/js/utils/create-vue-application.js b/arches/app/media/js/utils/create-vue-application.js
index 2f1620adc48..21bd78507ac 100644
--- a/arches/app/media/js/utils/create-vue-application.js
+++ b/arches/app/media/js/utils/create-vue-application.js
@@ -10,8 +10,9 @@ import Tooltip from 'primevue/tooltip';
import { createApp } from 'vue';
import { createGettext } from "vue3-gettext";
-import arches from 'arches';
import { DEFAULT_THEME } from "@/arches/themes/default.ts";
+import generateArchesURL from '@/arches/utils/generate-arches-url.ts';
+
export default async function createVueApplication(vueComponent, themeConfiguration) {
/**
@@ -27,7 +28,8 @@ export default async function createVueApplication(vueComponent, themeConfigurat
* TODO: cbyrd #10501 - we should add an event listener that will re-fetch i18n data
* and rebuild the app when a specific event is fired from the LanguageSwitcher component.
**/
- return fetch(arches.urls.api_get_frontend_i18n_data).then(function(resp) {
+
+ return fetch(generateArchesURL("get_frontend_i18n_data")).then(function(resp) {
if (!resp.ok) {
throw new Error(resp.statusText);
}
diff --git a/arches/app/src/arches/utils/generate-arches-url.test.ts b/arches/app/src/arches/utils/generate-arches-url.test.ts
new file mode 100644
index 00000000000..68fab4bf889
--- /dev/null
+++ b/arches/app/src/arches/utils/generate-arches-url.test.ts
@@ -0,0 +1,56 @@
+import { describe, it, expect } from "vitest";
+import generateArchesURL from "@/arches/utils/generate-arches-url.ts";
+
+// @ts-expect-error ARCHES_URLS is defined globally
+global.ARCHES_URLS = {
+ example_url: "/{language_code}/admin/example/{id}",
+ another_url: "/admin/another/{id}",
+ multi_interpolation_url:
+ "/{language_code}/resource/{resource_id}/edit/{field_id}/version/{version_id}",
+};
+
+describe("generateArchesURL", () => {
+ it("should return a valid URL with specified language code and parameters", () => {
+ const result = generateArchesURL("example_url", { id: "123" }, "fr");
+ expect(result).toBe("/fr/admin/example/123");
+ });
+
+ it("should use the lang attribute when no language code is provided", () => {
+ Object.defineProperty(document.documentElement, "lang", {
+ value: "de",
+ configurable: true,
+ });
+
+ const result = generateArchesURL("example_url", { id: "123" });
+ expect(result).toBe("/de/admin/example/123");
+ });
+
+ it("should throw an error if the URL name is not found", () => {
+ expect(() =>
+ generateArchesURL("invalid_url", { id: "123" }, "fr"),
+ ).toThrowError("Key 'invalid_url' not found in JSON object");
+ });
+
+ it("should replace URL parameters correctly", () => {
+ const result = generateArchesURL("another_url", { id: "456" });
+ expect(result).toBe("/admin/another/456");
+ });
+
+ it("should handle URLs without language code placeholder", () => {
+ const result = generateArchesURL("another_url", { id: "789" });
+ expect(result).toBe("/admin/another/789");
+ });
+
+ it("should handle multiple interpolations in the URL", () => {
+ const result = generateArchesURL(
+ "multi_interpolation_url",
+ {
+ resource_id: "42",
+ field_id: "name",
+ version_id: "7",
+ },
+ "es",
+ );
+ expect(result).toBe("/es/resource/42/edit/name/version/7");
+ });
+});
diff --git a/arches/app/src/arches/utils/generate-arches-url.ts b/arches/app/src/arches/utils/generate-arches-url.ts
new file mode 100644
index 00000000000..148272868bc
--- /dev/null
+++ b/arches/app/src/arches/utils/generate-arches-url.ts
@@ -0,0 +1,27 @@
+export default function (
+ urlName: string,
+ urlParams = {},
+ languageCode?: string,
+) {
+ // @ts-expect-error ARCHES_URLS is defined globally
+ let url = ARCHES_URLS[urlName];
+
+ if (!url) {
+ throw new Error(`Key '${urlName}' not found in JSON object`);
+ }
+
+ if (url.includes("{language_code}")) {
+ if (!languageCode) {
+ const htmlLang = document.documentElement.lang;
+ languageCode = htmlLang.split("-")[0];
+ }
+
+ url = url.replace("{language_code}", languageCode);
+ }
+
+ Object.entries(urlParams).forEach(([key, value]) => {
+ url = url.replace(new RegExp(`{${key}}`, "g"), value);
+ });
+
+ return url;
+}
diff --git a/arches/app/templates/base-root.htm b/arches/app/templates/base-root.htm
index 0413379c6b1..d85e6a13f40 100644
--- a/arches/app/templates/base-root.htm
+++ b/arches/app/templates/base-root.htm
@@ -22,7 +22,7 @@
-
+
{% block head %}
@@ -77,7 +77,6 @@
{% endblock pre_require_js %}
{% block arches_modules %}
- {% include "arches_urls.htm" %}
{% endblock arches_modules %}
{% if main_script %}
diff --git a/arches/app/templates/base.htm b/arches/app/templates/base.htm
index 990736c20e3..d6c2651623f 100644
--- a/arches/app/templates/base.htm
+++ b/arches/app/templates/base.htm
@@ -17,16 +17,9 @@
-->
{% extends "base-root.htm" %}
-{% load static %}
{% load i18n %}
-{% load webpack_static from webpack_loader %}
{% load render_bundle from webpack_loader %}
-
-
-
-
-
{% if use_livereload %}
{% endif %}
@@ -61,5 +54,4 @@
{% block arches_modules %}
{% include 'javascript.htm' %}
-{% endblock arches_modules %}
-
+{% endblock arches_modules %}
\ No newline at end of file
diff --git a/arches/app/utils/frontend_configuration_utils.py b/arches/app/utils/frontend_configuration_utils.py
new file mode 100644
index 00000000000..cc9969307b7
--- /dev/null
+++ b/arches/app/utils/frontend_configuration_utils.py
@@ -0,0 +1,219 @@
+import json
+import os
+import re
+import site
+import sys
+
+from django.conf import settings
+from django.urls import get_resolver, URLPattern, URLResolver
+from django.urls.resolvers import RegexPattern, RoutePattern, LocalePrefixPattern
+
+from arches.settings_utils import list_arches_app_names, list_arches_app_paths
+
+
+def generate_frontend_configuration():
+ try:
+ _generate_frontend_configuration_directory()
+ _generate_urls_json()
+ _generate_webpack_configuration()
+ _generate_tsconfig_paths()
+ except Exception as e:
+ # Ensures error message is shown if error encountered
+ sys.stderr.write(str(e))
+ raise e
+
+
+def _generate_frontend_configuration_directory():
+ destination_dir = os.path.realpath(
+ os.path.join(_get_base_path(), "..", "frontend_configuration")
+ )
+
+ os.makedirs(destination_dir, exist_ok=True)
+
+
+def _generate_urls_json():
+ def generate_human_readable_urls(patterns, prefix="", namespace=""):
+ def join_paths(*args):
+ components = []
+
+ for index, segment in enumerate(args):
+ if index == 0: # Only strip trailing slash for the first segment
+ segment = segment.rstrip("/")
+ elif (
+ index == len(args) - 1
+ ): # Only strip leading slash for the last segment
+ segment = segment.lstrip("/")
+ else: # Strip both slashes for middle segments
+ segment = segment.strip("/")
+
+ components.append(segment)
+
+ return "/".join(filter(None, components))
+
+ def interpolate_route(pattern):
+ if isinstance(pattern, RoutePattern):
+ return re.sub(r"<(?:[^:]+:)?([^>]+)>", r"{\1}", pattern._route)
+ elif isinstance(pattern, RegexPattern):
+ regex = pattern._regex.lstrip("^").rstrip("$")
+
+ # Replace named capture groups (e.g., (?P)) with {param}
+ regex = re.sub(r"\(\?P<(\w+)>[^)]+\)", r"{\1}", regex)
+
+ # Remove non-capturing groups (e.g., (?:...))
+ regex = re.sub(r"\(\?:[^\)]+\)", "", regex)
+
+ # Remove character sets (e.g., [0-9])
+ regex = re.sub(r"\[[^\]]]+\]", "", regex)
+
+ # Remove backslashes (used to escape special characters in regex)
+ regex = regex.replace("\\", "")
+
+ # Remove regex-specific special characters
+ regex = re.sub(r"[\^\$\+\*\?\(\)]", "", regex)
+
+ return regex
+
+ result = {}
+
+ for pattern in patterns:
+ if isinstance(pattern, URLPattern):
+ url_name = f"{namespace}{pattern.name}"
+ url_path = "/" + join_paths(prefix, interpolate_route(pattern.pattern))
+ result[url_name] = url_path
+
+ elif isinstance(pattern, URLResolver):
+ current_namespace = (
+ f"{namespace}{pattern.namespace}:"
+ if pattern.namespace
+ else namespace
+ )
+
+ if isinstance(
+ pattern.pattern, LocalePrefixPattern
+ ): # Handles i18n_patterns
+ new_prefix = join_paths(prefix, "{language_code}")
+ else:
+ new_prefix = join_paths(prefix, interpolate_route(pattern.pattern))
+
+ sub_result = generate_human_readable_urls(
+ pattern.url_patterns, new_prefix, current_namespace
+ )
+ result.update(sub_result)
+
+ return result
+
+ resolver = get_resolver()
+ human_readable_urls = generate_human_readable_urls(resolver.url_patterns)
+
+ # Manual additions
+ human_readable_urls["static_url"] = settings.STATIC_URL
+ human_readable_urls["media_url"] = settings.MEDIA_URL
+
+ destination_path = os.path.realpath(
+ os.path.join(_get_base_path(), "..", "frontend_configuration", "urls.json")
+ )
+
+ with open(destination_path, "w") as file:
+ json.dump(
+ {
+ "_comment": "This is a generated file. Do not edit directly.",
+ **{
+ url_name: human_readable_urls[url_name]
+ for url_name in sorted(human_readable_urls)
+ },
+ },
+ file,
+ indent=4,
+ )
+
+
+def _generate_webpack_configuration():
+ app_root_path = os.path.realpath(settings.APP_ROOT)
+ root_dir_path = os.path.realpath(settings.ROOT_DIR)
+
+ arches_app_names = list_arches_app_names()
+ arches_app_paths = list_arches_app_paths()
+
+ destination_path = os.path.realpath(
+ os.path.join(
+ _get_base_path(), "..", "frontend_configuration", "webpack-metadata.json"
+ )
+ )
+
+ with open(destination_path, "w") as file:
+ json.dump(
+ {
+ "_comment": "This is a generated file. Do not edit directly.",
+ "APP_RELATIVE_PATH": os.path.relpath(app_root_path),
+ "APP_ROOT": app_root_path,
+ "ARCHES_APPLICATIONS": arches_app_names,
+ "ARCHES_APPLICATIONS_PATHS": dict(
+ zip(arches_app_names, arches_app_paths, strict=True)
+ ),
+ "SITE_PACKAGES_DIRECTORY": site.getsitepackages()[0],
+ "PUBLIC_SERVER_ADDRESS": settings.PUBLIC_SERVER_ADDRESS,
+ "ROOT_DIR": root_dir_path,
+ "STATIC_URL": settings.STATIC_URL,
+ "WEBPACK_DEVELOPMENT_SERVER_PORT": settings.WEBPACK_DEVELOPMENT_SERVER_PORT,
+ },
+ file,
+ indent=4,
+ )
+
+
+def _generate_tsconfig_paths():
+ base_path = _get_base_path()
+ root_dir_path = os.path.realpath(settings.ROOT_DIR)
+
+ path_lookup = dict(
+ zip(list_arches_app_names(), list_arches_app_paths(), strict=True)
+ )
+
+ tsconfig_paths_data = {
+ "_comment": "This is a generated file. Do not edit directly.",
+ "compilerOptions": {
+ "paths": {
+ "@/arches/*": [
+ os.path.join(
+ ".",
+ os.path.relpath(
+ root_dir_path,
+ os.path.join(base_path, ".."),
+ ),
+ "app",
+ "src",
+ "arches",
+ "*",
+ )
+ ],
+ **{
+ os.path.join("@", path_name, "*"): [
+ os.path.join(
+ ".",
+ os.path.relpath(path, os.path.join(base_path, "..")),
+ "src",
+ path_name,
+ "*",
+ )
+ ]
+ for path_name, path in path_lookup.items()
+ },
+ "*": ["./node_modules/*"],
+ }
+ },
+ }
+
+ destination_path = os.path.realpath(
+ os.path.join(base_path, "..", "frontend_configuration", "tsconfig-paths.json")
+ )
+
+ with open(destination_path, "w") as file:
+ json.dump(tsconfig_paths_data, file, indent=4)
+
+
+def _get_base_path():
+ return (
+ os.path.realpath(settings.ROOT_DIR)
+ if settings.APP_NAME == "Arches"
+ else os.path.realpath(settings.APP_ROOT)
+ )
diff --git a/arches/apps.py b/arches/apps.py
index 7f7a7ce81e2..277ae579689 100644
--- a/arches/apps.py
+++ b/arches/apps.py
@@ -11,7 +11,9 @@
from semantic_version import SimpleSpec, Version
from arches import __version__
-from arches.settings_utils import generate_frontend_configuration
+from arches.app.utils.frontend_configuration_utils import (
+ generate_frontend_configuration,
+)
class ArchesAppConfig(AppConfig):
diff --git a/arches/install/arches-templates/.gitignore b/arches/install/arches-templates/.gitignore
index c90cc0d864c..89a6e0398d5 100644
--- a/arches/install/arches-templates/.gitignore
+++ b/arches/install/arches-templates/.gitignore
@@ -15,5 +15,4 @@ webpack-stats.json
*.egg-info
.DS_STORE
CACHE
-.tsconfig-paths.json
-.frontend-configuration-settings.json
+frontend_configuration
diff --git a/arches/install/arches-templates/project_name/apps.py-tpl b/arches/install/arches-templates/project_name/apps.py-tpl
index 6d933396a45..6588c66c61d 100644
--- a/arches/install/arches-templates/project_name/apps.py-tpl
+++ b/arches/install/arches-templates/project_name/apps.py-tpl
@@ -1,7 +1,7 @@
from django.apps import AppConfig
from django.conf import settings
-from arches.settings_utils import generate_frontend_configuration
+from arches.app.utils.frontend_configuration_utils import generate_frontend_configuration
class {{ project_name_title_case }}Config(AppConfig):
diff --git a/arches/install/arches-templates/tsconfig.json b/arches/install/arches-templates/tsconfig.json
index c223f2c6ad0..67e112fd782 100644
--- a/arches/install/arches-templates/tsconfig.json
+++ b/arches/install/arches-templates/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "./.tsconfig-paths.json",
+ "extends": "./frontend_configuration/tsconfig-paths.json",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
diff --git a/arches/install/arches-templates/vitest.config.mts b/arches/install/arches-templates/vitest.config.mts
index 08370501933..f333c494e26 100644
--- a/arches/install/arches-templates/vitest.config.mts
+++ b/arches/install/arches-templates/vitest.config.mts
@@ -22,10 +22,7 @@ function generateConfig(): Promise {
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
];
- const rawData = fs.readFileSync(
- path.join(filePath, '.frontend-configuration-settings.json'),
- 'utf-8'
- );
+ const rawData = fs.readFileSync(path.join(__dirname, 'frontend_configuration', 'webpack-metadata.json'), 'utf-8');
const parsedData = JSON.parse(rawData);
const alias: { [key: string]: string } = {
diff --git a/arches/install/arches-templates/webpack/webpack.common.js b/arches/install/arches-templates/webpack/webpack.common.js
index cb62dd40ed7..79886fcbc22 100644
--- a/arches/install/arches-templates/webpack/webpack.common.js
+++ b/arches/install/arches-templates/webpack/webpack.common.js
@@ -14,13 +14,13 @@ const { buildFilepathLookup } = require('./webpack-utils/build-filepath-lookup')
module.exports = () => {
return new Promise((resolve, _reject) => {
- // BEGIN get data from `.frontend-configuration-settings.json`
+ // BEGIN get data from `webpack-metadata.json`
- const rawData = fs.readFileSync(Path.join(__dirname, "..", '.frontend-configuration-settings.json'), 'utf-8');
+ const rawData = fs.readFileSync(Path.join(__dirname, "..", "frontend_configuration", 'webpack-metadata.json'), 'utf-8');
const parsedData = JSON.parse(rawData);
- console.log('Data imported from .frontend-configuration-settings.json:', parsedData);
-
+ console.log('Data imported from webpack-metadata.json:', parsedData);
+
global.APP_ROOT = parsedData['APP_ROOT'];
global.ARCHES_APPLICATIONS = parsedData['ARCHES_APPLICATIONS'];
global.ARCHES_APPLICATIONS_PATHS = parsedData['ARCHES_APPLICATIONS_PATHS'];
@@ -30,7 +30,7 @@ module.exports = () => {
global.PUBLIC_SERVER_ADDRESS = parsedData['PUBLIC_SERVER_ADDRESS'];
global.WEBPACK_DEVELOPMENT_SERVER_PORT = parsedData['WEBPACK_DEVELOPMENT_SERVER_PORT'];
- // END get data from `.frontend-configuration-settings.json`
+ // END get data from `webpack-metadata.json`
// BEGIN workaround for handling node_modules paths in arches-core vs projects
let PROJECT_RELATIVE_NODE_MODULES_PATH;
@@ -242,8 +242,8 @@ module.exports = () => {
const universalConstants = {
APP_ROOT_DIRECTORY: JSON.stringify(APP_ROOT).replace(/\\/g, '/'),
- ARCHES_CORE_DIRECTORY: JSON.stringify(ROOT_DIR).replace(/\\/g, '/'),
ARCHES_APPLICATIONS: JSON.stringify(ARCHES_APPLICATIONS),
+ ARCHES_CORE_DIRECTORY: JSON.stringify(ROOT_DIR).replace(/\\/g, '/'),
SITE_PACKAGES_DIRECTORY: JSON.stringify(SITE_PACKAGES_DIRECTORY).replace(/\\/g, '/'),
};
@@ -279,6 +279,15 @@ module.exports = () => {
plugins: [
new CleanWebpackPlugin(),
new webpack.DefinePlugin(universalConstants),
+ new webpack.DefinePlugin({
+ ARCHES_URLS: webpack.DefinePlugin.runtimeValue(
+ () => fs.readFileSync(
+ Path.resolve(__dirname, PROJECT_RELATIVE_NODE_MODULES_PATH, '..', 'frontend_configuration', 'urls.json'),
+ 'utf-8'
+ ),
+ true // should be re-evaluated on rebuild
+ ),
+ }),
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
@@ -290,9 +299,9 @@ module.exports = () => {
jquery: Path.resolve(__dirname, PROJECT_RELATIVE_NODE_MODULES_PATH, 'jquery', 'dist', 'jquery.min')
}),
new MiniCssExtractPlugin(),
- new BundleTracker({
+ new BundleTracker({
path: Path.resolve(__dirname),
- filename: 'webpack-stats.json'
+ filename: 'webpack-stats.json'
}),
new VueLoaderPlugin(),
],
@@ -351,6 +360,7 @@ module.exports = () => {
{
test: /\.css$/,
exclude: [
+ /node_modules/,
Path.resolve(__dirname, APP_ROOT, 'media', 'css'),
Path.resolve(__dirname, ROOT_DIR, 'app', 'media', 'css'),
...archesApplicationsCSSFilepaths
@@ -450,7 +460,7 @@ module.exports = () => {
if (serverAddress.charAt(serverAddress.length - 1) === '/') {
serverAddress = serverAddress.slice(0, -1)
}
-
+
resp = await fetch(serverAddress + templatePath);
if (resp.status === 500) {
diff --git a/arches/install/arches-templates/webpack/webpack.config.dev.js b/arches/install/arches-templates/webpack/webpack.config.dev.js
index 2e3da3f6c00..50b98bc30df 100644
--- a/arches/install/arches-templates/webpack/webpack.config.dev.js
+++ b/arches/install/arches-templates/webpack/webpack.config.dev.js
@@ -43,7 +43,16 @@ module.exports = () => {
}),
new StylelintPlugin({
files: Path.join('src', '**/*.s?(a|c)ss'),
- })
+ }),
+ {
+ apply: (compiler) => {
+ compiler.hooks.afterCompile.tap('WatchArchesUrlsPlugin', (compilation) => {
+ compilation.fileDependencies.add(
+ Path.resolve(__dirname, APP_ROOT, '..', 'frontend_configuration', 'urls.json')
+ );
+ });
+ },
+ },
],
}));
});
diff --git a/arches/management/commands/updateproject.py b/arches/management/commands/updateproject.py
index 33c4a1f93b9..bb836e70302 100644
--- a/arches/management/commands/updateproject.py
+++ b/arches/management/commands/updateproject.py
@@ -4,6 +4,8 @@
from django.core.management.base import BaseCommand
from arches.app.models.system_settings import settings
+from arches.app.models.system_settings import settings
+
class Command(BaseCommand): # pragma: no cover
"""
@@ -22,6 +24,55 @@ def handle(self, *args, **options):
self.stdout.write("Operation aborted.")
def update_to_v8(self):
+ # Removes unnecessary files
+ self.stdout.write("Removing unnecessary files...")
+
+ for file_to_delete in [
+ ".frontend-configuration-settings.json",
+ ".tsconfig-paths.json",
+ ]:
+ if os.path.exists(os.path.join(settings.APP_ROOT, "..", file_to_delete)):
+ self.stdout.write("Deleting {}".format(file_to_delete))
+ os.remove(os.path.join(settings.APP_ROOT, "..", file_to_delete))
+
+ declarations_test_file_path = os.path.join(
+ settings.APP_ROOT, "src", settings.APP_NAME, "declarations.test.ts"
+ )
+
+ if os.path.exists(declarations_test_file_path):
+ self.stdout.write("Deleting {}".format("declarations.test.ts"))
+ os.remove(declarations_test_file_path)
+
+ self.stdout.write("Done!")
+
+ # Updates webpack
+ self.stdout.write("Creating updated webpack directory...")
+
+ if os.path.isdir(os.path.join(settings.APP_ROOT, "..", "webpack")):
+ shutil.rmtree(
+ os.path.join(settings.APP_ROOT, "..", "webpack"), ignore_errors=True
+ )
+
+ shutil.copytree(
+ os.path.join(settings.ROOT_DIR, "install", "arches-templates", "webpack"),
+ os.path.join(settings.APP_ROOT, "..", "webpack"),
+ )
+
+ self.stdout.write("Done!")
+
+ # Replaces tsconfig.json
+ self.stdout.write("Updating tsconfig.json...")
+
+ if os.path.exists(os.path.join(settings.APP_ROOT, "..", "tsconfig.json")):
+ os.remove(os.path.join(settings.APP_ROOT, "..", "tsconfig.json"))
+
+ shutil.copy2(
+ os.path.join(
+ settings.ROOT_DIR, "install", "arches-templates", "tsconfig.json"
+ ),
+ os.path.join(settings.APP_ROOT, "..", "tsconfig.json"),
+ )
+
# Replaces vitest config files
self.stdout.write("Updating vitest configuration files...")
@@ -39,19 +90,6 @@ def update_to_v8(self):
self.stdout.write("Done!")
- # Removes unnecessary files
- self.stdout.write("Removing unnecessary files...")
-
- declarations_test_file_path = os.path.join(
- settings.APP_ROOT, "src", settings.APP_NAME, "declarations.test.ts"
- )
-
- if os.path.exists(declarations_test_file_path):
- self.stdout.write("Deleting {}".format("declarations.test.ts"))
- os.remove(declarations_test_file_path)
-
- self.stdout.write("Done!")
-
# Update certain lines in GitHub Actions workflows.
self.stdout.write("Updating GitHub Actions...")
action_path = os.path.join(
diff --git a/arches/settings_utils.py b/arches/settings_utils.py
index d4d40ad7205..9ad59295116 100644
--- a/arches/settings_utils.py
+++ b/arches/settings_utils.py
@@ -1,11 +1,7 @@
-import json
import os
-import site
import sys
-from contextlib import contextmanager
from django.apps import apps
-from django.conf import settings
from django.contrib.staticfiles.finders import AppDirectoriesFinder
@@ -134,91 +130,3 @@ def build_templates_config(
# Ensures error message is shown if error encountered in webpack build
sys.stdout.write(str(e))
raise e
-
-
-def generate_frontend_configuration():
- try:
- app_root_path = os.path.realpath(settings.APP_ROOT)
- root_dir_path = os.path.realpath(settings.ROOT_DIR)
-
- arches_app_names = list_arches_app_names()
- arches_app_paths = list_arches_app_paths()
- path_lookup = dict(zip(arches_app_names, arches_app_paths, strict=True))
-
- frontend_configuration_settings_data = {
- "_comment": "This is a generated file. Do not edit directly.",
- "APP_ROOT": app_root_path,
- "APP_RELATIVE_PATH": os.path.relpath(app_root_path),
- "ARCHES_APPLICATIONS": arches_app_names,
- "ARCHES_APPLICATIONS_PATHS": path_lookup,
- "SITE_PACKAGES_DIRECTORY": site.getsitepackages()[0],
- "PUBLIC_SERVER_ADDRESS": settings.PUBLIC_SERVER_ADDRESS,
- "ROOT_DIR": root_dir_path,
- "STATIC_URL": settings.STATIC_URL,
- "WEBPACK_DEVELOPMENT_SERVER_PORT": settings.WEBPACK_DEVELOPMENT_SERVER_PORT,
- }
-
- if settings.APP_NAME == "Arches":
- base_path = root_dir_path
- else:
- base_path = app_root_path
-
- frontend_configuration_settings_path = os.path.realpath(
- os.path.join(base_path, "..", ".frontend-configuration-settings.json")
- )
- sys.stdout.write(
- f"Writing frontend configuration to: {frontend_configuration_settings_path} \n"
- )
-
- with open(
- frontend_configuration_settings_path,
- "w",
- ) as file:
- json.dump(frontend_configuration_settings_data, file, indent=4)
-
- tsconfig_paths_data = {
- "_comment": "This is a generated file. Do not edit directly.",
- "compilerOptions": {
- "paths": {
- "@/arches/*": [
- os.path.join(
- ".",
- os.path.relpath(
- root_dir_path,
- os.path.join(base_path, ".."),
- ),
- "app",
- "src",
- "arches",
- "*",
- )
- ],
- **{
- os.path.join("@", path_name, "*"): [
- os.path.join(
- ".",
- os.path.relpath(path, os.path.join(base_path, "..")),
- "src",
- path_name,
- "*",
- )
- ]
- for path_name, path in path_lookup.items()
- },
- "*": ["./node_modules/*"],
- }
- },
- }
-
- tsconfig_path = os.path.realpath(
- os.path.join(base_path, "..", ".tsconfig-paths.json")
- )
- sys.stdout.write(f"Writing tsconfig path data to: {tsconfig_path} \n")
-
- with open(tsconfig_path, "w") as file:
- json.dump(tsconfig_paths_data, file, indent=4)
-
- except Exception as e:
- # Ensures error message is shown if error encountered
- sys.stderr.write(str(e))
- raise e
diff --git a/releases/8.0.0.md b/releases/8.0.0.md
index c438346ee2d..07a229bb0ae 100644
--- a/releases/8.0.0.md
+++ b/releases/8.0.0.md
@@ -23,6 +23,7 @@ Arches 8.0.0 Release Notes
- Support more expressive plugin URLs [#11320](https://github.com/archesproject/arches/issues/11320)
- Make node aliases not nullable [#10437](https://github.com/archesproject/arches/issues/10437)
- Concepts API no longer responds with empty body for error conditions [#11519](https://github.com/archesproject/arches/issues/11519)
+- Generates static `urls.json` file for frontend consumption [#11567](https://github.com/archesproject/arches/issues/11567)
- Removes sample index from new projects, updates test coverage behavior [#11591](https://github.com/archesproject/arches/issues/11519)
### Dependency changes
@@ -62,6 +63,8 @@ JavaScript:
- `ensure_userprofile_exists()` was removed from the `Tile` model.
+- The `arches` object is now only available in frontend components that are mounted in templates that inherit from `base.htm`. If your project contains components that are mounted in templates that inherit directly from `base-root.htm`, they will need to be updated to use `generateArchesURL` and the settings API instead.
+
- The following fields are no longer nullable. If you have custom SQL (or Python code that uses direct ORM operations to bypass model methods, etc.), you will need to set these fields directly on creation:
- `Node.alias`
@@ -77,37 +80,63 @@ JavaScript:
1. In settings.py, add the following key to `DATABASES` to [improve indexing performance](https://github.com/archesproject/arches/issues/11382):
```
- "OPTIONS": {
- "options": "-c cursor_tuple_fraction=1",
- },
+ DATABASES = {
+ "default": {
+ ... # other entries
+ "OPTIONS": {
+ "options": "-c cursor_tuple_fraction=1",
+ },
+ }
+ }
```
+1. Update `.gitignore`:
+ 1. Remove `.tsconfig-paths.json` and `.frontend-configuration-settings.json`
+ 2. Add `frontend_configuration`
-1. Update your frontend dependencies:
+1. Replace the `generate_frontend_configuration` import statement in `apps.py`:
```
- rm -rf node_modules package-lock.json
- npm install
+ from arches.settings_utils import generate_frontend_configuration
```
-1. Within your project, with your Python 3 virtual environment activated run:
- ```
- python manage.py migrate
- ```
+ Should be updated to:
-1. Then run:
```
- python manage.py updateproject
+ from arches.app.utils.frontend_configuration_utils import generate_frontend_configuration
```
-3. Create editable_future_graphs for your Resource Models using the command `python manage.py graph create_editable_future_graphs`. This will publish new versions of each Graph.
+1. Within your project, with your Python 3 virtual environment activated:
+ 1. Migrate your database:
+ ```
+ python manage.py migrate
+ ```
+
+ 1. Then update your project:
+ ```
+ python manage.py updateproject
+ ```
-4. Update your Graph publications and Resource instances to point to the newly published Graphs by running `python manage.py graph publish --update -ui`
+ 1. Create editable_future_graphs for your Resource Models. This will publish new versions of each Graph.
+ ```
+ python manage.py graph create_editable_future_graphs
+ ```
-5. Within your project with your Python 3 virtual environment activated:
+ 1. Update your Graph publications and Resource instances to point to the newly published Graphs:
+ ```
+ python manage.py graph publish --update -ui
+ ```
+
+ 1. Then reindex your database
+ ```
+ python manage.py es reindex_database
+ ```
+
+1. Update your frontend dependencies:
```
- python manage.py es reindex_database
+ rm -rf node_modules package-lock.json
+ npm install
```
-6. Run `npm start` or `npm run build_development` to rebuild your static asset bundle:
+1. Run `npm start` or `npm run build_development` to rebuild your static asset bundle:
- If running your project in development:
- `npm start` will build the frontend of the application and then start a webpack development server
- `npm run build_development` will build a development bundle for the frontend assests of the application -- this should complete in less than 2 minutes
diff --git a/tsconfig.json b/tsconfig.json
index c223f2c6ad0..67e112fd782 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "./.tsconfig-paths.json",
+ "extends": "./frontend_configuration/tsconfig-paths.json",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
diff --git a/vitest.config.mts b/vitest.config.mts
index 08370501933..f333c494e26 100644
--- a/vitest.config.mts
+++ b/vitest.config.mts
@@ -22,10 +22,7 @@ function generateConfig(): Promise {
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
];
- const rawData = fs.readFileSync(
- path.join(filePath, '.frontend-configuration-settings.json'),
- 'utf-8'
- );
+ const rawData = fs.readFileSync(path.join(__dirname, 'frontend_configuration', 'webpack-metadata.json'), 'utf-8');
const parsedData = JSON.parse(rawData);
const alias: { [key: string]: string } = {
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
index cb62dd40ed7..79886fcbc22 100644
--- a/webpack/webpack.common.js
+++ b/webpack/webpack.common.js
@@ -14,13 +14,13 @@ const { buildFilepathLookup } = require('./webpack-utils/build-filepath-lookup')
module.exports = () => {
return new Promise((resolve, _reject) => {
- // BEGIN get data from `.frontend-configuration-settings.json`
+ // BEGIN get data from `webpack-metadata.json`
- const rawData = fs.readFileSync(Path.join(__dirname, "..", '.frontend-configuration-settings.json'), 'utf-8');
+ const rawData = fs.readFileSync(Path.join(__dirname, "..", "frontend_configuration", 'webpack-metadata.json'), 'utf-8');
const parsedData = JSON.parse(rawData);
- console.log('Data imported from .frontend-configuration-settings.json:', parsedData);
-
+ console.log('Data imported from webpack-metadata.json:', parsedData);
+
global.APP_ROOT = parsedData['APP_ROOT'];
global.ARCHES_APPLICATIONS = parsedData['ARCHES_APPLICATIONS'];
global.ARCHES_APPLICATIONS_PATHS = parsedData['ARCHES_APPLICATIONS_PATHS'];
@@ -30,7 +30,7 @@ module.exports = () => {
global.PUBLIC_SERVER_ADDRESS = parsedData['PUBLIC_SERVER_ADDRESS'];
global.WEBPACK_DEVELOPMENT_SERVER_PORT = parsedData['WEBPACK_DEVELOPMENT_SERVER_PORT'];
- // END get data from `.frontend-configuration-settings.json`
+ // END get data from `webpack-metadata.json`
// BEGIN workaround for handling node_modules paths in arches-core vs projects
let PROJECT_RELATIVE_NODE_MODULES_PATH;
@@ -242,8 +242,8 @@ module.exports = () => {
const universalConstants = {
APP_ROOT_DIRECTORY: JSON.stringify(APP_ROOT).replace(/\\/g, '/'),
- ARCHES_CORE_DIRECTORY: JSON.stringify(ROOT_DIR).replace(/\\/g, '/'),
ARCHES_APPLICATIONS: JSON.stringify(ARCHES_APPLICATIONS),
+ ARCHES_CORE_DIRECTORY: JSON.stringify(ROOT_DIR).replace(/\\/g, '/'),
SITE_PACKAGES_DIRECTORY: JSON.stringify(SITE_PACKAGES_DIRECTORY).replace(/\\/g, '/'),
};
@@ -279,6 +279,15 @@ module.exports = () => {
plugins: [
new CleanWebpackPlugin(),
new webpack.DefinePlugin(universalConstants),
+ new webpack.DefinePlugin({
+ ARCHES_URLS: webpack.DefinePlugin.runtimeValue(
+ () => fs.readFileSync(
+ Path.resolve(__dirname, PROJECT_RELATIVE_NODE_MODULES_PATH, '..', 'frontend_configuration', 'urls.json'),
+ 'utf-8'
+ ),
+ true // should be re-evaluated on rebuild
+ ),
+ }),
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
@@ -290,9 +299,9 @@ module.exports = () => {
jquery: Path.resolve(__dirname, PROJECT_RELATIVE_NODE_MODULES_PATH, 'jquery', 'dist', 'jquery.min')
}),
new MiniCssExtractPlugin(),
- new BundleTracker({
+ new BundleTracker({
path: Path.resolve(__dirname),
- filename: 'webpack-stats.json'
+ filename: 'webpack-stats.json'
}),
new VueLoaderPlugin(),
],
@@ -351,6 +360,7 @@ module.exports = () => {
{
test: /\.css$/,
exclude: [
+ /node_modules/,
Path.resolve(__dirname, APP_ROOT, 'media', 'css'),
Path.resolve(__dirname, ROOT_DIR, 'app', 'media', 'css'),
...archesApplicationsCSSFilepaths
@@ -450,7 +460,7 @@ module.exports = () => {
if (serverAddress.charAt(serverAddress.length - 1) === '/') {
serverAddress = serverAddress.slice(0, -1)
}
-
+
resp = await fetch(serverAddress + templatePath);
if (resp.status === 500) {
diff --git a/webpack/webpack.config.dev.js b/webpack/webpack.config.dev.js
index 2e3da3f6c00..50b98bc30df 100644
--- a/webpack/webpack.config.dev.js
+++ b/webpack/webpack.config.dev.js
@@ -43,7 +43,16 @@ module.exports = () => {
}),
new StylelintPlugin({
files: Path.join('src', '**/*.s?(a|c)ss'),
- })
+ }),
+ {
+ apply: (compiler) => {
+ compiler.hooks.afterCompile.tap('WatchArchesUrlsPlugin', (compilation) => {
+ compilation.fileDependencies.add(
+ Path.resolve(__dirname, APP_ROOT, '..', 'frontend_configuration', 'urls.json')
+ );
+ });
+ },
+ },
],
}));
});