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..810dd90a8e1
--- /dev/null
+++ b/arches/app/utils/frontend_configuration_utils.py
@@ -0,0 +1,198 @@
+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="", result={}):
+ def join_paths(*args):
+ return "/".join(filter(None, (arg.strip("/") for arg in args)))
+
+ 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 (^, $, +, *, ?, (), etc.)
+ regex = re.sub(r"[\^\$\+\*\?\(\)]", "", regex)
+
+ return regex.strip("/")
+
+ for pattern in patterns:
+ if isinstance(pattern, URLPattern):
+ if pattern.name:
+ result[f"{namespace}{pattern.name}"] = "/" + join_paths(
+ prefix, interpolate_route(pattern.pattern)
+ )
+ elif isinstance(pattern, URLResolver):
+ current_namespace = namespace + (
+ f":{pattern.namespace}:" if pattern.namespace else ""
+ )
+
+ 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))
+
+ generate_human_readable_urls(
+ pattern.url_patterns, new_prefix, current_namespace, 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_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/app/utils/index_database.py b/arches/app/utils/index_database.py
index 81fdae83450..69a6616d1cc 100644
--- a/arches/app/utils/index_database.py
+++ b/arches/app/utils/index_database.py
@@ -5,9 +5,6 @@
import uuid
import pyprind
import sys
-import django
-
-django.setup()
from datetime import datetime
from django.db import connection, connections
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 4dda8725d1a..dd171bcf69b 100644
--- a/arches/install/arches-templates/vitest.config.mts
+++ b/arches/install/arches-templates/vitest.config.mts
@@ -19,7 +19,7 @@ function generateConfig(): Promise {
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
];
- 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);
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 eadff5409d3..1ac119043f7 100644
--- a/arches/management/commands/updateproject.py
+++ b/arches/management/commands/updateproject.py
@@ -1,5 +1,10 @@
+import os
+import shutil
+
from django.core.management.base import BaseCommand
+from arches.app.models.system_settings import settings
+
class Command(BaseCommand): # pragma: no cover
"""
@@ -18,4 +23,45 @@ def handle(self, *args, **options):
self.stdout.write("Operation aborted.")
def update_to_v8(self):
- pass
+ # 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))
+
+ 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"),
+ )
+
+ self.stdout.write("Done!")
diff --git a/arches/settings_utils.py b/arches/settings_utils.py
index d8ceb934996..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,90 +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,
- "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 d8e203ec284..46c52b5af18 100644
--- a/releases/8.0.0.md
+++ b/releases/8.0.0.md
@@ -21,6 +21,7 @@ Arches 8.0.0 Release Notes
- Improve handling of longer model names [#11317](https://github.com/archesproject/arches/issues/11317)
- Support more expressive plugin URLs [#11320](https://github.com/archesproject/arches/issues/11320)
- 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)
### Dependency changes
```
@@ -57,6 +58,8 @@ JavaScript:
- `ensure_userprofile_exists()` was removed from the `Tile` model.
+- The `arches` object is no longer available in frontend components that exist outside of defined Arches patterns. If your project contains Vue components that are mounted on templates that inherit directly from `base-root.htm`, they will need to be updated to use `generateArchesURL` and the settings API instead.
+
### Upgrading Arches
1. You must be upgraded to at least version before proceeding. If you are on an earlier version, please refer to the upgrade process in the []()
@@ -69,9 +72,9 @@ 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",
- },
+ "OPTIONS": {
+ "options": "-c cursor_tuple_fraction=1",
+ },
```
1. Update your frontend dependencies:
@@ -80,21 +83,36 @@ JavaScript:
npm install
```
-2. Within your project, with your Python 3 virtual environment activated:
+1. Update `.gitignore`:
+ 1. Remove `.tsconfig-paths.json` and `.frontend-configuration-settings.json`
+ 2. Add `frontend_configuration`
+
+1. Replace the `generate_frontend_configuration` import statement in `apps.py`:
+ ```
+ from arches.settings_utils import generate_frontend_configuration
+ ```
+
+ Should be updated to:
+
+ ```
+ from arches.app.utils.frontend_configuration_utils import generate_frontend_configuration
+ ```
+
+1. Within your project, with your Python 3 virtual environment activated:
```
python manage.py migrate
```
-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. 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.
-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. Update your Graph publications and Resource instances to point to the newly published Graphs by running `python manage.py graph publish --update -ui`
-5. Within your project with your Python 3 virtual environment activated:
+1. Within your project with your Python 3 virtual environment activated:
```
python manage.py es reindex_database
```
-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 4cf706fa8c1..c1e36b0fe1c 100644
--- a/vitest.config.mts
+++ b/vitest.config.mts
@@ -19,7 +19,7 @@ function generateConfig(): Promise {
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
];
- 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);
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')
+ );
+ });
+ },
+ },
],
}));
});