generated from polymech/site-template
config
This commit is contained in:
parent
37c7a78d1e
commit
48458746a7
@ -23,7 +23,7 @@
|
||||
"format": "unix-time"
|
||||
}
|
||||
],
|
||||
"default": "2025-12-27T07:52:28.530Z"
|
||||
"default": "2025-12-27T08:18:11.303Z"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -134,7 +134,11 @@
|
||||
],
|
||||
"rtl_languages": [
|
||||
"ar"
|
||||
]
|
||||
],
|
||||
"osr_root": "${OSR_ROOT}"
|
||||
},
|
||||
"dev": {
|
||||
"file_server": "localhost:5000"
|
||||
},
|
||||
"i18n": {
|
||||
"store": "${OSR_ROOT}/i18n-store/store-${LANG}.json",
|
||||
@ -224,6 +228,23 @@
|
||||
"sizes_large": "(min-width: 1024px) 1024px, 1024vw",
|
||||
"sizes_regular": "(min-width: 400px) 400px, 400vw"
|
||||
}
|
||||
},
|
||||
"presets": {
|
||||
"slow": {
|
||||
"sizes_medium": "(min-width: 100px) 100px, 100vw",
|
||||
"sizes_thumbs": "(min-width: 80px) 80px, 80vw",
|
||||
"sizes_large": "(min-width: 320px) 320px, 320vw"
|
||||
},
|
||||
"medium": {
|
||||
"sizes_medium": "(min-width: 400px) 400px, 400vw",
|
||||
"sizes_thumbs": "(min-width: 120px) 120px, 120vw",
|
||||
"sizes_large": "(min-width: 1024px) 1024px, 1024vw"
|
||||
},
|
||||
"fast": {
|
||||
"sizes_medium": "(min-width: 1024px) 1024px, 1024vw",
|
||||
"sizes_thumbs": "(min-width: 180px) 180px, 180vw",
|
||||
"sizes_large": "(min-width: 1200px) 1200px, 1200vw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
app-config.local.json
Normal file
5
app-config.local.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"core": {
|
||||
"logging_namespace": "LOCAL_CONFIG_OVERRIDE"
|
||||
}
|
||||
}
|
||||
11
scripts/test-config-loader.ts
Normal file
11
scripts/test-config-loader.ts
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
import { loadConfig } from '../src/app/config-loader.js';
|
||||
import { I18N_SOURCE_LANGUAGE } from "../src/app/constants.js";
|
||||
|
||||
try {
|
||||
const config = loadConfig(I18N_SOURCE_LANGUAGE);
|
||||
console.log('LOGGING_NAMESPACE:', config.core.logging_namespace);
|
||||
console.log('SHOW_GALLERY:', config.features.show_gallery);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
|
||||
import { substitute } from "@polymech/commons/variables";
|
||||
import { appConfigSchema } from "./config.schema.js";
|
||||
@ -8,17 +10,44 @@ import type { AppConfig } from "./config.schema.js";
|
||||
|
||||
import { I18N_SOURCE_LANGUAGE } from "./constants.js"
|
||||
|
||||
const DEFAULT_CONFIG_PATH = path.resolve("./app-config.json");
|
||||
const LIBRARY_CONFIG_PATH = path.resolve("./app-config.json");
|
||||
const USER_CONFIG_DEFAULT_PATH = path.resolve("./app-config.local.json");
|
||||
|
||||
function deepMerge(target: any, source: any): any {
|
||||
if (typeof source !== 'object' || source === null) {
|
||||
return source; // Primitives or null overwrite
|
||||
}
|
||||
if (Array.isArray(source)) {
|
||||
return source; // Arrays overwrite
|
||||
}
|
||||
if (typeof target !== 'object' || target === null || Array.isArray(target)) {
|
||||
return source; // Target is not mergeable object, overwrite
|
||||
}
|
||||
|
||||
const result = { ...target };
|
||||
for (const key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
const val = source[key];
|
||||
if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
|
||||
result[key] = deepMerge(result[key], val);
|
||||
} else {
|
||||
result[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function loadConfig(
|
||||
locale: string = I18N_SOURCE_LANGUAGE,
|
||||
configPath: string = DEFAULT_CONFIG_PATH
|
||||
libraryPath: string = LIBRARY_CONFIG_PATH
|
||||
): AppConfig {
|
||||
let rawContent: string;
|
||||
// 1. Load Library Config (Defaults)
|
||||
let rawLibraryContent: string;
|
||||
try {
|
||||
rawContent = fs.readFileSync(configPath, 'utf-8');
|
||||
rawLibraryContent = fs.readFileSync(libraryPath, 'utf-8');
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read config file at ${configPath}: ${error}`);
|
||||
throw new Error(`Failed to read library config file at ${libraryPath}: ${error}`);
|
||||
}
|
||||
|
||||
const variables = {
|
||||
@ -26,18 +55,53 @@ export function loadConfig(
|
||||
...process.env
|
||||
};
|
||||
|
||||
const substitutedContent = substitute(false, rawContent, variables);
|
||||
|
||||
let parsedConfig: unknown;
|
||||
const substitutedLibraryContent = substitute(false, rawLibraryContent, variables);
|
||||
let libraryConfig: any;
|
||||
try {
|
||||
parsedConfig = JSON.parse(substitutedContent);
|
||||
libraryConfig = JSON.parse(substitutedLibraryContent);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse config JSON after substitution: ${error}`);
|
||||
throw new Error(`Failed to parse library config JSON: ${error}`);
|
||||
}
|
||||
|
||||
const result = appConfigSchema.safeParse(parsedConfig);
|
||||
// 2. Parse CLI Arguments
|
||||
// We assume the caller might want to pass args, or we just grab process.argv
|
||||
// We cast to any because yargs returns a complex type
|
||||
const argv: any = yargs(hideBin(process.argv)).parseSync ? yargs(hideBin(process.argv)).parseSync() : (yargs(hideBin(process.argv)) as any).argv;
|
||||
|
||||
// 3. Determine User Config Path
|
||||
// Check for --config <path>
|
||||
const userConfigPath = argv.config ? path.resolve(argv.config) : USER_CONFIG_DEFAULT_PATH;
|
||||
|
||||
// 4. Load User Config (if exists)
|
||||
let userConfig: any = {};
|
||||
if (fs.existsSync(userConfigPath)) {
|
||||
try {
|
||||
const rawUserContent = fs.readFileSync(userConfigPath, 'utf-8');
|
||||
const substitutedUserContent = substitute(false, rawUserContent, variables);
|
||||
userConfig = JSON.parse(substitutedUserContent);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load or parse user config at ${userConfigPath}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Merge: Library <- User <- CLI
|
||||
// Note: yargs parses --config as part of argv, but also other flags like --core.logging_namespace
|
||||
// We filter out specific known CLI-only flags if needed, but config schema validation will drop unknown keys anyway?
|
||||
// Actually zod 'strip' is default in safeParse? No, usually it passes through unless strict().
|
||||
// We should probably rely on valid keys overwriting.
|
||||
|
||||
// CLI args often come with standard keys like '$0', '_' which we might want to exclude if we blindly merge.
|
||||
// However, deepMerge will add them.
|
||||
// Ideally we would only merge keys that exist in the schema, but dynamic is fine for now.
|
||||
|
||||
let mergedConfig = deepMerge(libraryConfig, userConfig);
|
||||
mergedConfig = deepMerge(mergedConfig, argv);
|
||||
|
||||
// 6. Validate
|
||||
const result = appConfigSchema.safeParse(mergedConfig);
|
||||
|
||||
if (!result.success) {
|
||||
// Pretty print error if possible or just message
|
||||
throw new Error(`Config validation failed: ${result.error.message}`);
|
||||
}
|
||||
|
||||
|
||||
19
src/app/config.d.ts
vendored
19
src/app/config.d.ts
vendored
@ -11,6 +11,7 @@ export interface AppConfig {
|
||||
shopify: Shopify;
|
||||
pages: Pages;
|
||||
core: Core;
|
||||
dev: Dev;
|
||||
i18n: I18N;
|
||||
products: Products;
|
||||
retail: Retail;
|
||||
@ -50,6 +51,7 @@ export interface Core {
|
||||
languages: string[];
|
||||
languages_prod: string[];
|
||||
rtl_languages: string[];
|
||||
osr_root: string;
|
||||
}
|
||||
|
||||
export interface Defaults {
|
||||
@ -58,6 +60,10 @@ export interface Defaults {
|
||||
contact: string;
|
||||
}
|
||||
|
||||
export interface Dev {
|
||||
file_server: string;
|
||||
}
|
||||
|
||||
export interface Ecommerce {
|
||||
brand: string;
|
||||
currencySymbol: string;
|
||||
@ -98,6 +104,7 @@ export interface NavigationButton {
|
||||
|
||||
export interface Optimization {
|
||||
image_settings: ImageSettings;
|
||||
presets: Presets;
|
||||
}
|
||||
|
||||
export interface ImageSettings {
|
||||
@ -113,6 +120,18 @@ export interface Gallery {
|
||||
sizes_regular: string;
|
||||
}
|
||||
|
||||
export interface Presets {
|
||||
slow: Fast;
|
||||
medium: Fast;
|
||||
fast: Fast;
|
||||
}
|
||||
|
||||
export interface Fast {
|
||||
sizes_medium: string;
|
||||
sizes_thumbs: string;
|
||||
sizes_large: string;
|
||||
}
|
||||
|
||||
export interface Osrl {
|
||||
env: string;
|
||||
env_dev: string;
|
||||
|
||||
@ -51,7 +51,12 @@ export const coreSchema = z.object({
|
||||
translate_content: z.boolean(),
|
||||
languages: z.array(z.string()),
|
||||
languages_prod: z.array(z.string()),
|
||||
rtl_languages: z.array(z.string())
|
||||
rtl_languages: z.array(z.string()),
|
||||
osr_root: z.string()
|
||||
});
|
||||
|
||||
export const devSchema = z.object({
|
||||
file_server: z.string()
|
||||
});
|
||||
|
||||
export const i18NSchema = z.object({
|
||||
@ -120,6 +125,12 @@ export const gallerySchema = z.object({
|
||||
sizes_regular: z.string()
|
||||
});
|
||||
|
||||
export const fastSchema = z.object({
|
||||
sizes_medium: z.string(),
|
||||
sizes_thumbs: z.string(),
|
||||
sizes_large: z.string()
|
||||
});
|
||||
|
||||
export const blogSchema = z.object({
|
||||
store: z.string()
|
||||
});
|
||||
@ -161,6 +172,12 @@ export const imageSettingsSchema = z.object({
|
||||
lightbox: gallerySchema
|
||||
});
|
||||
|
||||
export const presetsSchema = z.object({
|
||||
slow: fastSchema,
|
||||
medium: fastSchema,
|
||||
fast: fastSchema
|
||||
});
|
||||
|
||||
export const homeSchema = z.object({
|
||||
hero: z.string(),
|
||||
_blog: blogSchema
|
||||
@ -171,7 +188,8 @@ export const pagesSchema = z.object({
|
||||
});
|
||||
|
||||
export const optimizationSchema = z.object({
|
||||
image_settings: imageSettingsSchema
|
||||
image_settings: imageSettingsSchema,
|
||||
presets: presetsSchema
|
||||
});
|
||||
|
||||
export const appConfigSchema = z.object({
|
||||
@ -187,6 +205,7 @@ export const appConfigSchema = z.object({
|
||||
shopify: shopifySchema,
|
||||
pages: pagesSchema,
|
||||
core: coreSchema,
|
||||
dev: devSchema,
|
||||
i18n: i18NSchema,
|
||||
products: productsSchema,
|
||||
retail: retailSchema,
|
||||
|
||||
@ -11,8 +11,8 @@ 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 OSR_ROOT = () => path.resolve(resolve(config.core.osr_root))
|
||||
export const FILE_SERVER_DEV = config.dev.file_server
|
||||
|
||||
export const LOGGING_NAMESPACE = config.core.logging_namespace
|
||||
export const TRANSLATE_CONTENT = config.core.translate_content
|
||||
|
||||
@ -24,26 +24,15 @@ const imagesSchema = z.object({
|
||||
|
||||
type Images = z.infer<typeof imagesSchema>;
|
||||
|
||||
export const IMAGE_PRESET: Images =
|
||||
import { loadConfig } from './config-loader.js'
|
||||
import { I18N_SOURCE_LANGUAGE } from "./constants.js"
|
||||
|
||||
// Load config
|
||||
const config = loadConfig(I18N_SOURCE_LANGUAGE)
|
||||
|
||||
export const IMAGE_PRESET: Images =
|
||||
{
|
||||
[E_BROADBAND_SPEED.SLOW]: {
|
||||
// For 2g connections: smaller image widths help performance. (Middle East & Africa)
|
||||
sizes_medium: "(min-width: 100px) 100px, 100vw",
|
||||
sizes_thumbs: "(min-width: 80px) 80px, 80vw",
|
||||
sizes_large: "(min-width: 320px) 320px, 320vw",
|
||||
},
|
||||
[E_BROADBAND_SPEED.MEDIUM]:
|
||||
{
|
||||
// For 3g connections: a moderate size image for a balance of quality and speed.
|
||||
sizes_medium: "(min-width: 400px) 400px, 400vw",
|
||||
sizes_thumbs: "(min-width: 120px) 120px, 120vw",
|
||||
sizes_large: "(min-width: 1024px) 1024px, 1024vw",
|
||||
},
|
||||
[E_BROADBAND_SPEED.FAST]:
|
||||
{
|
||||
// For 4g connections: larger images for high-resolution displays.
|
||||
sizes_medium: "(min-width: 1024px) 1024px, 1024vw",
|
||||
sizes_thumbs: "(min-width: 180px) 180px, 180vw",
|
||||
sizes_large: "(min-width: 1200px) 1200px, 1200vw"
|
||||
}
|
||||
[E_BROADBAND_SPEED.SLOW]: config.optimization.presets.slow,
|
||||
[E_BROADBAND_SPEED.MEDIUM]: config.optimization.presets.medium,
|
||||
[E_BROADBAND_SPEED.FAST]: config.optimization.presets.fast
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user