From 37c7a78d1efa73b5953d852bee9f8a130a83f97c Mon Sep 17 00:00:00 2001 From: babayaga Date: Sat, 27 Dec 2025 08:56:09 +0100 Subject: [PATCH] config --- .astro/collections/resources.schema.json | 2 +- app-config.json | 111 +++++++++++++++++ src/app/config-loader.ts | 45 +++++++ src/app/config.d.ts | 94 ++++++++++++++ src/app/config.schema.ts | 96 ++++++++++++++- src/app/config.ts | 149 +++++++++-------------- src/app/constants.ts | 1 + 7 files changed, 405 insertions(+), 93 deletions(-) create mode 100644 src/app/config-loader.ts create mode 100644 src/app/constants.ts diff --git a/.astro/collections/resources.schema.json b/.astro/collections/resources.schema.json index 2a1ceb1..38f6810 100644 --- a/.astro/collections/resources.schema.json +++ b/.astro/collections/resources.schema.json @@ -23,7 +23,7 @@ "format": "unix-time" } ], - "default": "2025-12-27T07:01:36.602Z" + "default": "2025-12-27T07:52:28.530Z" }, "description": { "type": "string", diff --git a/app-config.json b/app-config.json index b16a1fe..d72b8c9 100644 --- a/app-config.json +++ b/app-config.json @@ -114,5 +114,116 @@ "store": "resources" } } + }, + "core": { + "logging_namespace": "polymech-site", + "translate_content": true, + "languages": [ + "en", + "ar", + "de", + "ja", + "es", + "zh", + "fr" + ], + "languages_prod": [ + "en", + "es", + "fr" + ], + "rtl_languages": [ + "ar" + ] + }, + "i18n": { + "store": "${OSR_ROOT}/i18n-store/store-${LANG}.json", + "cache": true, + "asset_path": "${SRC_DIR}/${SRC_NAME}-${DST_LANG}${SRC_EXT}" + }, + "products": { + "root": "${OSR_ROOT}/products", + "howto_migration": "./data/last.json", + "glob": "**/config.json", + "enabled": "${OSR_ROOT}/products/config/machines.json" + }, + "retail": { + "library_branch": "site-prod", + "projects_branch": "projects" + }, + "rss": { + "title": "Polymech RSS Feed", + "description": "" + }, + "osrl": { + "env": "astro-release", + "env_dev": "astro-debug", + "module_name": "polymech.io", + "lang_flavor": "osr", + "product_profile": "./src/app/profile.json" + }, + "features": { + "show_description": false, + "show_license": true, + "show_renderings": true, + "show_tabs": false, + "show_gallery": true, + "show_files": true, + "show_specs": true, + "show_checkout": true, + "show_contact": true, + "show_3d_preview": true, + "show_resources": true, + "show_debug": false, + "show_samples": true, + "show_readme": false, + "show_related": true, + "show_showcase": true, + "show_screenshots": true + }, + "defaults": { + "image_url": "https://picsum.photos/640/640", + "license": "CERN Open Source Hardware License", + "contact": "sales@plastic-hub.com" + }, + "cad": { + "cache": true, + "export_configurations": true, + "export_sub_components": true, + "renderer": "solidworks", + "renderer_view": "Render", + "renderer_quality": 1, + "extensions": [ + ".STEP", + ".html" + ], + "model_ext": ".tree.json", + "default_configuration": "Default", + "main_match": "${product}/cad*/*Global*.+(SLDASM)", + "cam_main_match": "${product}/cad*/*-CNC*.+(SLDASM)" + }, + "assets": { + "cad_url": "${OSR_MACHINES_ASSETS_URL}/${file}", + "url": "${OSR_MACHINES_ASSETS_URL}/products/${product_rel_min}/${file}", + "item_url_r": "${OSR_MACHINES_ASSETS_URL}/${ITEM_REL}/${assetPath}/${filePath}", + "item_url": "http://${FILE_SERVER_DEV}/${ITEM_REL}/${assetPath}/${filePath}" + }, + "optimization": { + "image_settings": { + "gallery": { + "show_title": true, + "show_description": false, + "sizes_thumb": "(min-width: 120px) 120px, 120vw", + "sizes_large": "(min-width: 1024px) 1024px, 1024vw", + "sizes_regular": "(min-width: 400px) 400px, 400vw" + }, + "lightbox": { + "show_title": true, + "show_description": true, + "sizes_thumb": "(min-width: 120px) 120px, 120vw", + "sizes_large": "(min-width: 1024px) 1024px, 1024vw", + "sizes_regular": "(min-width: 400px) 400px, 400vw" + } + } } } \ No newline at end of file diff --git a/src/app/config-loader.ts b/src/app/config-loader.ts new file mode 100644 index 0000000..b90ad96 --- /dev/null +++ b/src/app/config-loader.ts @@ -0,0 +1,45 @@ + +import * as fs from "fs"; +import * as path from "path"; + +import { substitute } from "@polymech/commons/variables"; +import { appConfigSchema } from "./config.schema.js"; +import type { AppConfig } from "./config.schema.js"; + +import { I18N_SOURCE_LANGUAGE } from "./constants.js" + +const DEFAULT_CONFIG_PATH = path.resolve("./app-config.json"); + +export function loadConfig( + locale: string = I18N_SOURCE_LANGUAGE, + configPath: string = DEFAULT_CONFIG_PATH +): AppConfig { + let rawContent: string; + try { + rawContent = fs.readFileSync(configPath, 'utf-8'); + } catch (error) { + throw new Error(`Failed to read config file at ${configPath}: ${error}`); + } + + const variables = { + LANG: locale, + ...process.env + }; + + const substitutedContent = substitute(false, rawContent, variables); + + let parsedConfig: unknown; + try { + parsedConfig = JSON.parse(substitutedContent); + } catch (error) { + throw new Error(`Failed to parse config JSON after substitution: ${error}`); + } + + const result = appConfigSchema.safeParse(parsedConfig); + + if (!result.success) { + throw new Error(`Config validation failed: ${result.error.message}`); + } + + return result.data; +} diff --git a/src/app/config.d.ts b/src/app/config.d.ts index 0aa3ee2..d3d41f5 100644 --- a/src/app/config.d.ts +++ b/src/app/config.d.ts @@ -10,6 +10,52 @@ export interface AppConfig { metadata: Metadata; shopify: Shopify; pages: Pages; + core: Core; + i18n: I18N; + products: Products; + retail: Retail; + rss: Rss; + osrl: Osrl; + features: { [key: string]: boolean }; + defaults: Defaults; + cad: Cad; + assets: Assets; + optimization: Optimization; +} + +export interface Assets { + cad_url: string; + url: string; + item_url_r: string; + item_url: string; +} + +export interface Cad { + cache: boolean; + export_configurations: boolean; + export_sub_components: boolean; + renderer: string; + renderer_view: string; + renderer_quality: number; + extensions: string[]; + model_ext: string; + default_configuration: string; + main_match: string; + cam_main_match: string; +} + +export interface Core { + logging_namespace: string; + translate_content: boolean; + languages: string[]; + languages_prod: string[]; + rtl_languages: string[]; +} + +export interface Defaults { + image_url: string; + license: string; + contact: string; } export interface Ecommerce { @@ -23,6 +69,12 @@ export interface FooterLeft { text: string; } +export interface I18N { + store: string; + cache: boolean; + asset_path: string; +} + export interface Metadata { country: string; city: string; @@ -44,6 +96,31 @@ export interface NavigationButton { link: string; } +export interface Optimization { + image_settings: ImageSettings; +} + +export interface ImageSettings { + gallery: Gallery; + lightbox: Gallery; +} + +export interface Gallery { + show_title: boolean; + show_description: boolean; + sizes_thumb: string; + sizes_large: string; + sizes_regular: string; +} + +export interface Osrl { + env: string; + env_dev: string; + module_name: string; + lang_flavor: string; + product_profile: string; +} + export interface Pages { home: Home; } @@ -62,6 +139,23 @@ export interface Params { copyright: string; } +export interface Products { + root: string; + howto_migration: string; + glob: string; + enabled: string; +} + +export interface Retail { + library_branch: string; + projects_branch: string; +} + +export interface Rss { + title: string; + description: string; +} + export interface Settings { search: boolean; account: boolean; diff --git a/src/app/config.schema.ts b/src/app/config.schema.ts index 6eaf776..ca68651 100644 --- a/src/app/config.schema.ts +++ b/src/app/config.schema.ts @@ -46,6 +46,80 @@ export const metadataSchema = z.object({ keywords: z.string() }); +export const coreSchema = z.object({ + logging_namespace: z.string(), + translate_content: z.boolean(), + languages: z.array(z.string()), + languages_prod: z.array(z.string()), + rtl_languages: z.array(z.string()) +}); + +export const i18NSchema = z.object({ + store: z.string(), + cache: z.boolean(), + asset_path: z.string() +}); + +export const productsSchema = z.object({ + root: z.string(), + howto_migration: z.string(), + glob: z.string(), + enabled: z.string() +}); + +export const retailSchema = z.object({ + library_branch: z.string(), + projects_branch: z.string() +}); + +export const rssSchema = z.object({ + title: z.string(), + description: z.string() +}); + +export const osrlSchema = z.object({ + env: z.string(), + env_dev: z.string(), + module_name: z.string(), + lang_flavor: z.string(), + product_profile: z.string() +}); + +export const defaultsSchema = z.object({ + image_url: z.string(), + license: z.string(), + contact: z.string() +}); + +export const cadSchema = z.object({ + cache: z.boolean(), + export_configurations: z.boolean(), + export_sub_components: z.boolean(), + renderer: z.string(), + renderer_view: z.string(), + renderer_quality: z.number(), + extensions: z.array(z.string()), + model_ext: z.string(), + default_configuration: z.string(), + main_match: z.string(), + cam_main_match: z.string() +}); + +export const assetsSchema = z.object({ + cad_url: z.string(), + url: z.string(), + item_url_r: z.string(), + item_url: z.string() +}); + +export const gallerySchema = z.object({ + show_title: z.boolean(), + show_description: z.boolean(), + sizes_thumb: z.string(), + sizes_large: z.string(), + sizes_regular: z.string() +}); + export const blogSchema = z.object({ store: z.string() }); @@ -82,6 +156,11 @@ export const shopifySchema = z.object({ collections: collectionsSchema }); +export const imageSettingsSchema = z.object({ + gallery: gallerySchema, + lightbox: gallerySchema +}); + export const homeSchema = z.object({ hero: z.string(), _blog: blogSchema @@ -91,6 +170,10 @@ export const pagesSchema = z.object({ home: homeSchema }); +export const optimizationSchema = z.object({ + image_settings: imageSettingsSchema +}); + export const appConfigSchema = z.object({ site: siteSchema, footer_left: z.array(footerLeftSchema), @@ -102,7 +185,18 @@ export const appConfigSchema = z.object({ ecommerce: ecommerceSchema, metadata: metadataSchema, shopify: shopifySchema, - pages: pagesSchema + pages: pagesSchema, + core: coreSchema, + i18n: i18NSchema, + products: productsSchema, + retail: retailSchema, + rss: rssSchema, + osrl: osrlSchema, + features: z.record(z.string(), z.boolean()), + defaults: defaultsSchema, + cad: cadSchema, + assets: assetsSchema, + optimization: optimizationSchema }); export type AppConfig = z.infer; diff --git a/src/app/config.ts b/src/app/config.ts index f0b1930..44e2290 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -3,29 +3,35 @@ import { IMAGE_PRESET, E_BROADBAND_SPEED } from "./network.js" import { resolve, template } from '@polymech/commons' import { sync as read } from '@polymech/fs/read' import { sanitizeUri } from 'micromark-util-sanitize-uri' +import { loadConfig } from './config-loader.js' +import { I18N_SOURCE_LANGUAGE } from "./constants.js" + +export { I18N_SOURCE_LANGUAGE } + +// Load config +const config = loadConfig(I18N_SOURCE_LANGUAGE) export const OSR_ROOT = () => path.resolve(resolve("${OSR_ROOT}")) export const FILE_SERVER_DEV = 'localhost:5000' -export const LOGGING_NAMESPACE = 'polymech-site' -export const TRANSLATE_CONTENT = true -export const LANGUAGES = ['en', 'ar', 'de', 'ja', 'es', 'zh', 'fr'] -//export const LANGUAGES_PROD = ['en', 'es', 'ar', 'de', 'ja', 'zh', 'fr', 'nl', 'it', 'pt'] -export const LANGUAGES_PROD = ['en', 'es', 'fr'] -export const isRTL = (lang) => lang === 'ar' +export const LOGGING_NAMESPACE = config.core.logging_namespace +export const TRANSLATE_CONTENT = config.core.translate_content +export const LANGUAGES = config.core.languages +export const LANGUAGES_PROD = config.core.languages_prod +export const isRTL = (lang) => config.core.rtl_languages.includes(lang) + // i18n constants -export const I18N_STORE = (root, lang) => `${root}/i18n-store/store-${lang}.json` -export const I18N_SOURCE_LANGUAGE = 'en' -export const I18N_CACHE = true -export const I18N_ASSET_PATH = "${SRC_DIR}/${SRC_NAME}-${DST_LANG}${SRC_EXT}" +export const I18N_STORE = (root, lang) => template(config.i18n.store, { root, lang, LANG: lang, OSR_ROOT: root }) +export const I18N_CACHE = config.i18n.cache +export const I18N_ASSET_PATH = config.i18n.asset_path // Products -export const HOWTO_MIGRATION = () => path.resolve(resolve("./data/last.json")) +export const HOWTO_MIGRATION = () => path.resolve(resolve(config.products.howto_migration)) // Products -export const PRODUCT_ROOT = () => path.resolve(resolve("${OSR_ROOT}/products")) +export const PRODUCT_ROOT = () => path.resolve(resolve(config.products.root)) export const PRODUCT_BRANCHES = read(path.join(PRODUCT_ROOT(), 'config/machines.json'), 'json') -export const PRODUCT_GLOB = '**/config.json' +export const PRODUCT_GLOB = config.products.glob // Product compiler export const PRODUCT_CONFIG = (product) => @@ -34,72 +40,52 @@ export const PRODUCT_CONFIG = (product) => product })) export const PRODUCT_DIR = (product) => path.resolve(resolve(`${PRODUCT_ROOT()}/${product}`)) -export const PRODUCT_HUGO_TEMPLATE = './osr/hugo/root.html' -export const PRODUCTS_TARGET_SRC = './src/content/en/retail' export const PRODUCTS_TARGET = (lang) => `./content/${lang}/products` // OSRL - Language export const IS_DEV = true -export const OSRL_ENV = 'astro-release' -export const OSRL_ENV_DEV = 'astro-debug' +export const OSRL_ENV = config.osrl.env +export const OSRL_ENV_DEV = config.osrl.env_dev export const OSRL_ENVIRONMENT = IS_DEV ? OSRL_ENV_DEV : OSRL_ENV -export const OSRL_MODULE_NAME = 'polymech.io' -export const OSRL_PRODUCT_PROFILE = './src/app/profile.json' -export const OSRL_LANG_FLAVOR = 'osr' +export const OSRL_MODULE_NAME = config.osrl.module_name +export const OSRL_PRODUCT_PROFILE = config.osrl.product_profile +export const OSRL_LANG_FLAVOR = config.osrl.lang_flavor // Products -export const ENABLED_PRODUCTS = "${OSR_ROOT}/products/config/machines.json" +export const ENABLED_PRODUCTS = resolve(config.products.enabled) export const PRODUCT_SPECS = (rel) => `${PRODUCT_ROOT()}/${rel}/specs.xlsx` // Tasks export const TASK_CONFIG_LOG_DIRECTORY = './config/' -// Task: compile:content -export const TASK_COMPILE_CONTENT = true -export const TASK_COMPILE_CONTENT_CACHE = false - -// Task - Logging -export const TASK_LOG_DIRECTORY = './logs/' // Task - Retail Config export const REGISTER_PRODUCT_TASKS = true -export const LIBARY_BRANCH = 'site-prod' -export const PROJECTS_BRANCH = 'projects' +export const LIBARY_BRANCH = config.retail.library_branch +export const PROJECTS_BRANCH = config.retail.projects_branch export const RETAIL_COMPILE_CACHE = false export const RETAIL_MEDIA_CACHE = true export const RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS = 'info' -export const ConvertProductMedia = true -export const TranslateProductAssets = false -export const PopulateProductDefaults = true - // CAD -export const CAD_MAIN_MATCH = (product) => `${product}/cad*/*Global*.+(SLDASM)` -export const CAD_CAM_MAIN_MATCH = (product) => `${product}/cad*/*-CNC*.+(SLDASM)` +export const CAD_MAIN_MATCH = (product) => template(config.cad.main_match, { product }) +export const CAD_CAM_MAIN_MATCH = (product) => template(config.cad.cam_main_match, { product }) -export const CAD_CACHE = true -export const CAD_EXPORT_CONFIGURATIONS = true -export const CAD_EXPORT_SUB_COMPONENTS = true export const CAD_MODEL_FILE_PATH = (SOURCE, CONFIGURATION = '') => - SOURCE.replace('.json', `${CONFIGURATION ? '-' + CONFIGURATION : ''}.tree.json`) -export const CAD_DEFAULT_CONFIGURATION = 'Default' -export const CAD_RENDERER = 'solidworks' -export const CAD_RENDERER_VIEW = 'Render' -export const CAD_RENDERER_QUALITY = 1 -export const CAD_EXTENSIONS = ['.STEP', '.html'] -export const CAD_MODEL_EXT = '.tree.json' + SOURCE.replace('.json', `${CONFIGURATION ? '-' + CONFIGURATION : ''}${config.cad.model_ext}`) + export const CAD_URL = (file: string, variables: Record) => - sanitizeUri(template("${OSR_MACHINES_ASSETS_URL}/${file}", { file, ...variables })) + sanitizeUri(template(config.assets.cad_url, { file, ...variables })) export const ASSET_URL = (file: string, variables: Record) => - sanitizeUri(template("${OSR_MACHINES_ASSETS_URL}/products/${product_rel_min}/${file}", { file, ...variables })) + sanitizeUri(template(config.assets.url, { file, ...variables })) export const ITEM_ASSET_URL_R = (variables: Record) => - template("${OSR_MACHINES_ASSETS_URL}/${ITEM_REL}/${assetPath}/${filePath}", variables) + template(config.assets.item_url_r, variables) export const ITEM_ASSET_URL = (variables: Record) => - template("http://${FILE_SERVER_DEV}/${ITEM_REL}/${assetPath}/${filePath}", variables) + template(config.assets.item_url, variables) //back compat - osr-cad export const parseBoolean = (value: string): boolean => { @@ -108,41 +94,38 @@ export const parseBoolean = (value: string): boolean => { ///////////////////////////////////////////// // // Rendering -export const SHOW_DESCRIPTION = false -export const SHOW_LICENSE = true -export const SHOW_RENDERINGS = true +export const SHOW_DESCRIPTION = config.features.show_description +export const SHOW_LICENSE = config.features.show_license +export const SHOW_RENDERINGS = config.features.show_renderings -export const SHOW_TABS = false -export const SHOW_GALLERY = true -export const SHOW_FILES = true -export const SHOW_SPECS = true -export const SHOW_CHECKOUT = true -export const SHOW_CONTACT = true -export const SHOW_3D_PREVIEW = true -export const SHOW_RESOURCES = true -export const SHOW_DEBUG = false -export const SHOW_SAMPLES = true -export const SHOW_README = false -export const SHOW_RELATED = true -export const SHOW_SHOWCASE = true -export const SHOW_SCREENSHOTS = true +export const SHOW_TABS = config.features.show_tabs +export const SHOW_GALLERY = config.features.show_gallery +export const SHOW_FILES = config.features.show_files +export const SHOW_SPECS = config.features.show_specs +export const SHOW_CHECKOUT = config.features.show_checkout +export const SHOW_CONTACT = config.features.show_contact +export const SHOW_3D_PREVIEW = config.features.show_3d_preview +export const SHOW_RESOURCES = config.features.show_resources +export const SHOW_DEBUG = config.features.show_debug +export const SHOW_SAMPLES = config.features.show_samples +export const SHOW_README = config.features.show_readme +export const SHOW_RELATED = config.features.show_related +export const SHOW_SHOWCASE = config.features.show_showcase +export const SHOW_SCREENSHOTS = config.features.show_screenshots ///////////////////////////////////////////// // // Plugins +// // RSS -export const RSS_CONFIG = -{ - title: 'Polymech RSS Feed', - description: '', -} +export const RSS_CONFIG = config.rss ///////////////////////////////////////////// // // Defaults -export const DEFAULT_IMAGE_URL = 'https://picsum.photos/640/640' +export const DEFAULT_IMAGE_URL = config.defaults.image_url export const default_image = () => { return { @@ -152,28 +135,12 @@ export const default_image = () => { } } -export const DEFAULT_LICENSE = `CERN Open Source Hardware License` -export const DEFAULT_CONTACT = `sales@plastic-hub.com` +export const DEFAULT_LICENSE = config.defaults.license +export const DEFAULT_CONTACT = config.defaults.contact ///////////////////////////////////////////// // // Optimization export const O_IMAGE = IMAGE_PRESET[E_BROADBAND_SPEED.MEDIUM] -export const IMAGE_SETTINGS = -{ - GALLERY: { - SHOW_TITLE: true, - SHOW_DESCRIPTION: false, - SIZES_THUMB: O_IMAGE.sizes_thumbs, - SIZES_LARGE: O_IMAGE.sizes_large, - SIZES_REGULAR: O_IMAGE.sizes_medium - }, - LIGHTBOX: { - SHOW_TITLE: true, - SHOW_DESCRIPTION: true, - SIZES_THUMB: O_IMAGE.sizes_thumbs, - SIZES_LARGE: O_IMAGE.sizes_large, - SIZES_REGULAR: O_IMAGE.sizes_medium - } -} \ No newline at end of file +export const IMAGE_SETTINGS = config.optimization.image_settings \ No newline at end of file diff --git a/src/app/constants.ts b/src/app/constants.ts new file mode 100644 index 0000000..b9ede8a --- /dev/null +++ b/src/app/constants.ts @@ -0,0 +1 @@ +export const I18N_SOURCE_LANGUAGE = 'en';