This commit is contained in:
babayaga
2025-12-27 08:56:09 +01:00
parent b1cc32847c
commit 37c7a78d1e
7 changed files with 405 additions and 93 deletions
+45
View File
@@ -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;
}
+94
View File
@@ -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;
+95 -1
View File
@@ -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<typeof appConfigSchema>;
+58 -91
View File
@@ -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<string, string>) =>
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<string, string>) =>
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<string, string>) =>
template("${OSR_MACHINES_ASSETS_URL}/${ITEM_REL}/${assetPath}/${filePath}", variables)
template(config.assets.item_url_r, variables)
export const ITEM_ASSET_URL = (variables: Record<string, string>) =>
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
}
}
export const IMAGE_SETTINGS = config.optimization.image_settings
+1
View File
@@ -0,0 +1 @@
export const I18N_SOURCE_LANGUAGE = 'en';