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

View File

@ -23,7 +23,7 @@
"format": "unix-time"
}
],
"default": "2025-12-27T07:01:36.602Z"
"default": "2025-12-27T07:52:28.530Z"
},
"description": {
"type": "string",

View File

@ -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"
}
}
}
}

45
src/app/config-loader.ts Normal file
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
src/app/config.d.ts vendored
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;

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>;

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
src/app/constants.ts Normal file
View File

@ -0,0 +1 @@
export const I18N_SOURCE_LANGUAGE = 'en';