hugo & tasks init

This commit is contained in:
2024-08-18 16:34:30 +02:00
parent 9751aed9ce
commit edd7fa7464
896 changed files with 102255 additions and 43 deletions
+4
View File
@@ -0,0 +1,4 @@
/node_modules
/coverage
*.log
.DS_Store
+102
View File
@@ -0,0 +1,102 @@
import * as path from 'path'
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'
import { logger as _logger } from '@plastichub/core/debug'
export const logger = _logger('ph-site')
import { createContent, ICompileTaskOptions } from '@plastichub/osr-tasks/tasks/compile'
import { productHugoTask, readProducts, registerProductTasks } from './product'
import { OSRL_MODULE_NAME, LANGUAGES, I18N_STORE, OSR_ROOT, TRANSLATE_CONTENT, TASK_COMPILE_CONTENT_CACHE, RETAIL_DEFAULT_BRANCH, I18N_SOURCE_LANGUAGE, REGISTER_RETAIL_TASKS, TASK_COMPILE_CONTENT } from './config'
import { writeTaskConfig } from './log'
import * as pMap from 'p-map'
// const _logger = createSubLogger(logger, logLevel, 'compile:content')
export const grunt = (grunt) => {
grunt.loadNpmTasks("grunt-extend-config")
const logLevel = grunt.option('logLevel') || 'warn'
logger.setSettings({ minLevel: logLevel })
const watch = grunt.option('watchContent')
// Pages - src/content/lang/**/*.md
const contentTask = (sourceLanguage, options: any = {}) => {
const config = {}
const src = `src/content/${sourceLanguage}/**/*.md`
const cwd = process.cwd()
const dst = path.resolve(`${cwd}/content/${sourceLanguage}`)
const profile = path.resolve(`./.osrl.json`)
const root = path.resolve(`./src/content/${sourceLanguage}`)
const compilerOptions: ICompileTaskOptions = {
output: dst,
debug: false,
cache: !!grunt.option('cache') || TASK_COMPILE_CONTENT_CACHE,
watchContent: watch,
module: OSRL_MODULE_NAME,
profile,
root,
...(options || {}),
env: 'library',
language: 'osr',
format: 'html',
sourceLanguage: sourceLanguage,
logLevel: logLevel,
variables: {
cwd,
targetLanguage: sourceLanguage,
sourceLanguage: sourceLanguage,
i18n: I18N_STORE(OSR_ROOT(), sourceLanguage)
}
}
let defaultOptions = {
src: [src],
options: compilerOptions
}
const onCompiled = (src, dst, content) => content
const onCompiledDone = async (src, dst, options, content) => {
return await pMap(LANGUAGES, async (dstLanguage) => {
const opts: ICompileTaskOptions = {
...defaultOptions.options,
output: path.resolve(`${process.cwd()}/content/${dstLanguage}/`),
sourceLanguage: sourceLanguage,
targetLanguage: dstLanguage,
variables: {
...defaultOptions.options.variables,
cwd: process.cwd(),
i18n: I18N_STORE(OSR_ROOT(), dstLanguage),
sourceLanguage: sourceLanguage,
targetLanguage: dstLanguage,
}
};
const content = await createContent(src, opts)
return content
}, { concurrency: 1 })
}
config[`content-${sourceLanguage}`] = {
...defaultOptions,
options: {
...defaultOptions.options,
onCompiled: TRANSLATE_CONTENT ? onCompiled : undefined,
onCompileDone: TRANSLATE_CONTENT ? onCompiledDone : undefined
}
}
grunt.extendConfig({ compile: config })
grunt.registerTask(`content-${sourceLanguage}`, `compile:content-${sourceLanguage}`)
writeTaskConfig(`compile_content-${sourceLanguage}`, config)
}
grunt.initConfig({
pkg: grunt.file.readJSON("package.json"),
compile: {}
})
contentTask(I18N_SOURCE_LANGUAGE)
grunt.registerTask('content', ['compile:content-en'])
////////////////////////////////////////////////////////////
// Products
const product_item_tasks = []
const productTasks = (items) =>
items.forEach((i) => productHugoTask(grunt, i, {}, product_item_tasks))
const _products = readProducts(grunt.option('branch') || RETAIL_DEFAULT_BRANCH)
TASK_COMPILE_CONTENT && productTasks(_products)
REGISTER_RETAIL_TASKS && registerProductTasks(grunt)
require("@plastichub/osr-tasks").initConfig(grunt, {})
}
module.exports = grunt
+60
View File
@@ -0,0 +1,60 @@
import * as path from 'path'
import { resolve } from '@plastichub/osr-cli-commons/fs'
export const OSR_ROOT = () => path.resolve(resolve("${OSR_ROOT}"))
// Supported languages
export const TRANSLATE_CONTENT = true // translate regular pages
export const LANGUAGES = ['de']
// i18n constants
export const I18N_STORE = (root, lang) => `${root}/i18n-store/store-${lang}.json`
export const I18N_SOURCE_LANGUAGE = 'en'
// Product compiler
export const PRODUCT_ROOT = () => path.resolve(resolve("${OSR_ROOT}/products"))
export const PRODUCT_CONFIG = (product) =>
path.resolve(resolve("${OSR_ROOT}/products/${product}/config.json", false,
{
product
}))
export const PRODUCT_DIR = (product) =>
path.resolve(resolve("${OSR_ROOT}/products/${product}", false,
{
product
}))
export const IS_DEV = true
export const OSRL_ENV = 'bazar-release'
export const OSRL_ENV_DEV = 'hugo-debug'
export const OSRL_ENVIRONMENT = IS_DEV ? OSRL_ENV_DEV : OSRL_ENV
export const PRODUCT_HUGO_TEMPLATE = './osr/hugo/root.html'
export const PRODUCTS_TARGET_SRC = './src/content/en/retail'
// OSRL - Language
export const OSRL_MODULE_NAME = 'osr-site'
export const OSRL_PRODUCT_PROFILE = '${root}/.osrl.json'
export const OSRL_LANG_FLAVOR = 'osr'
// Products
export const ENABLED_PRODUCTS = "./config/machines.json"
// Tasks
export const TASK_CONFIG_LOG_DIRECTORY = './config/'
// Task: compile:content
export const TASK_COMPILE_CONTENT = false
export const TASK_COMPILE_CONTENT_CACHE = true
// Task - Logging
export const TASK_LOG_DIRECTORY = './logs/'
// Task - Retail Config
export const REGISTER_RETAIL_TASKS = true
export const RETAIL_DEFAULT_BRANCH = 'current'
export const RETAIL_COMPILE_CACHE = false
export const RETAIL_MEDIA_CACHE = true
export const RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS = 'info'
export const ConvertProductMedia = false
export const TranslateProductAssets = false
export const PopulateProductDefaults = false
+83
View File
@@ -0,0 +1,83 @@
import {
I18N_SOURCE_LANGUAGE, I18N_STORE,
OSR_ROOT
} from './config'
import { IOptions as IOptionsI18n } from '@plastichub/osr-i18n/types'
import { CONFIG_DEFAULT } from '@plastichub/osr-cli-commons'
import { translateDeepL, getTranslation, translate } from '@plastichub/osr-i18n/lib/translate'
import { sync as read } from '@plastichub/fs/read'
import { sync as write } from '@plastichub/fs/write'
import { sanitize } from '@plastichub/osr-i18n/_cli'
import { createHash } from 'crypto'
const removeNonPrintableCharacters = (text: string): string => text.replace(/[^\x20-\x7E]/g, '')
export const clean = (text: string = "") => text.trim()
export const hash = (text: string) => createHash('md5').update(clean(text)).digest('base64')
export const store = (storePath: string, text: string, file: string = '') => {
const _hash: string = hash(text)
const store = read(storePath, 'json') || {}
store[_hash] = clean(text)
write(storePath, store)
}
export const translateText = async (text: string, srcLang: string, dstLang: string, storePath: string) => {
if (text.length === 0) {
return ''
}
if (srcLang === dstLang) {
store(storePath, text)
return
}
const config: any = CONFIG_DEFAULT()
text = clean(text)
const _hash: string = hash(text)
const db = read(storePath, 'json') || {}
if (db[text]) {
return db[text]
}
const out = await translateDeepL(
text,
srcLang,
dstLang,
{
...config.deepl
}, {
}, "")
const translation = getTranslation((out as any), false)
if (translation) {
db[text] = translation
write(storePath, db)
return translation
}
}
export const translateString = (str: string, srcLang: string, dstLanguage: string) => {
const translateProductAssets = async () => {
const config: any = CONFIG_DEFAULT()
if (dstLanguage === I18N_SOURCE_LANGUAGE) {
return
}
const i18nOptions: IOptionsI18n = {
srcLang: I18N_SOURCE_LANGUAGE,
dstLang: dstLanguage,
store: I18N_STORE(OSR_ROOT(), dstLanguage),
noCache: true,
api_key: config.deepl.auth_key,
logLevel: 'warn'
}
const ret = await translate(sanitize(i18nOptions) as any)
return ret
}
}
+499
View File
@@ -0,0 +1,499 @@
export interface ExifData {
name: string;
value: string;
}
export interface ImageSEOData {
src: string;
alt: string;
title: string;
caption?: string;
fileName: string;
format: string;
size: string;
metadata: {
location?: string;
camera?: string;
keywords: string[];
exifData: ExifData[];
};
}
export interface GalleryImage
{
name: string
url: string
thumb: string
responsive: string
meta: Meta
keywords: string[]
description: string
alt?: string
title?: string
height?: number
width?: number
}
export interface Meta {
format: string
width: number
height: number
space: string
channels: number
depth: string
density: number
chromaSubsampling: string
isProgressive: boolean
resolutionUnit: string
hasProfile: boolean
hasAlpha: boolean
orientation: number
exif: Exif
}
export interface Exif {
file: File
jfif: Jfif
exif: Exif2
gps: Gps
}
export interface File {
"Bits Per Sample": BitsPerSample
"Image Height": ImageHeight
"Image Width": ImageWidth
"Color Components": ColorComponents
Subsampling: Subsampling
FileType: FileType
}
export interface BitsPerSample {
value: number
description: string
}
export interface ImageHeight {
value: number
description: string
}
export interface ImageWidth {
value: number
description: string
}
export interface ColorComponents {
value: number
description: string
}
export interface Subsampling {
description: string
}
export interface FileType {
value: string
description: string
}
export interface Jfif {
"JFIF Version": JfifVersion
"Resolution Unit": ResolutionUnit
XResolution: Xresolution
YResolution: Yresolution
"JFIF Thumbnail Width": JfifThumbnailWidth
"JFIF Thumbnail Height": JfifThumbnailHeight
}
export interface JfifVersion {
value: number
description: string
}
export interface ResolutionUnit {
value: number
description: string
}
export interface Xresolution {
value: number
description: string
}
export interface Yresolution {
value: number
description: string
}
export interface JfifThumbnailWidth {
value: number
description: string
}
export interface JfifThumbnailHeight {
value: number
description: string
}
export interface Exif2 {
ImageDescription: ImageDescription
Make: Make
Model: Model
Orientation: Orientation
XResolution: Xresolution2
YResolution: Yresolution2
ResolutionUnit: ResolutionUnit2
Software: Software
DateTime: DateTime
YCbCrPositioning: YcbCrPositioning
"Exif IFD Pointer": ExifIfdPointer
"GPS Info IFD Pointer": GpsInfoIfdPointer
XPTitle: Xptitle
XPSubject: Xpsubject
Padding: Padding
ExposureTime: ExposureTime
FNumber: Fnumber
ExposureProgram: ExposureProgram
ISOSpeedRatings: IsospeedRatings
ExifVersion: ExifVersion
DateTimeOriginal: DateTimeOriginal
DateTimeDigitized: DateTimeDigitized
ComponentsConfiguration: ComponentsConfiguration
ExposureBiasValue: ExposureBiasValue
MeteringMode: MeteringMode
LightSource: LightSource
Flash: Flash
FocalLength: FocalLength
SubSecTime: SubSecTime
SubSecTimeOriginal: SubSecTimeOriginal
SubSecTimeDigitized: SubSecTimeDigitized
FlashpixVersion: FlashpixVersion
ColorSpace: ColorSpace
PixelXDimension: PixelXdimension
PixelYDimension: PixelYdimension
ExposureMode: ExposureMode
WhiteBalance: WhiteBalance
DigitalZoomRatio: DigitalZoomRatio
FocalLengthIn35mmFilm: FocalLengthIn35mmFilm
SceneCaptureType: SceneCaptureType
GPSLatitudeRef: GpslatitudeRef
GPSLatitude: Gpslatitude
GPSLongitudeRef: GpslongitudeRef
GPSLongitude: Gpslongitude
GPSAltitude: Gpsaltitude
}
export interface ImageDescription {
id: number
description: string
}
export interface Make {
id: number
description: string
}
export interface Model {
id: number
description: string
}
export interface Orientation {
id: number
value: number
description: string
}
export interface Xresolution2 {
id: number
description: string
}
export interface Yresolution2 {
id: number
description: string
}
export interface ResolutionUnit2 {
id: number
value: number
description: string
}
export interface Software {
id: number
description: string
}
export interface DateTime {
id: number
description: string
}
export interface YcbCrPositioning {
id: number
value: number
description: string
}
export interface ExifIfdPointer {
id: number
value: number
description: number
}
export interface GpsInfoIfdPointer {
id: number
value: number
description: number
}
export interface Xptitle {
id: number
description: string
}
export interface Xpsubject {
id: number
description: string
}
export interface Padding {
id: number
description: string
}
export interface ExposureTime {
id: number
description: string
}
export interface Fnumber {
id: number
description: string
}
export interface ExposureProgram {
id: number
value: number
description: string
}
export interface IsospeedRatings {
id: number
value: number
description: number
}
export interface ExifVersion {
id: number
description: string
}
export interface DateTimeOriginal {
id: number
description: string
}
export interface DateTimeDigitized {
id: number
description: string
}
export interface ComponentsConfiguration {
id: number
description: string
}
export interface ExposureBiasValue {
id: number
description: string
}
export interface MeteringMode {
id: number
value: number
description: string
}
export interface LightSource {
id: number
value: number
description: string
}
export interface Flash {
id: number
value: number
description: string
}
export interface FocalLength {
id: number
description: string
}
export interface SubSecTime {
id: number
description: string
}
export interface SubSecTimeOriginal {
id: number
description: string
}
export interface SubSecTimeDigitized {
id: number
description: string
}
export interface FlashpixVersion {
id: number
description: string
}
export interface ColorSpace {
id: number
value: number
description: string
}
export interface PixelXdimension {
id: number
value: number
description: number
}
export interface PixelYdimension {
id: number
value: number
description: number
}
export interface ExposureMode {
id: number
value: number
description: string
}
export interface WhiteBalance {
id: number
value: number
description: string
}
export interface DigitalZoomRatio {
id: number
description: string
}
export interface FocalLengthIn35mmFilm {
id: number
value: number
description: string
}
export interface SceneCaptureType {
id: number
value: number
description: string
}
export interface GpslatitudeRef {
id: number
description: string
}
export interface Gpslatitude {
id: number
description: number
}
export interface GpslongitudeRef {
id: number
description: string
}
export interface Gpslongitude {
id: number
description: number
}
export interface Gpsaltitude {
id: number
description: string
}
export interface Gps {
Latitude: number
Longitude: number
}
export const generateDefaultImageJSONLD = (imageData: ImageSEOData) =>{
return {
"@context": "https://schema.org",
"@type": "ImageObject",
"contentUrl": imageData.src,
"name": imageData.title,
"description": imageData.caption || imageData.alt,
"width": parseInt(imageData.size.split('x')[0]),
"height": parseInt(imageData.size.split('x')[1]),
"thumbnail": `https://example.com/thumbnails/${imageData.fileName}`,
"license": "https://example.com/license",
"acquireLicensePage": "https://example.com/buy-license",
"copyrightNotice": `© ${new Date().getFullYear()} Default Organization`,
"creator": {
"@type": "Person",
"name": "Default Creator Name"
},
"copyrightHolder": {
"@type": "Organization",
"name": "Default Organization Name"
},
"contentLocation": imageData.metadata.location || "Unknown location",
"datePublished": new Date().toISOString().split('T')[0],
"exifData": imageData.metadata.exifData.length > 0 ? imageData.metadata.exifData : [
{
"@type": "PropertyValue",
"name": "Camera",
"value": imageData.metadata.camera || "Unknown camera"
},
{
"@type": "PropertyValue",
"name": "Keywords",
"value": imageData.metadata.keywords.join(', ')
}
]
};
}
// Example usage
const imageData: ImageSEOData = {
src: "https://example.com/image.jpg",
alt: "A beautiful scenery",
title: "Beautiful Scenery",
caption: "A beautiful scenery with mountains and a lake.",
fileName: "scenery.jpg",
format: "image/jpeg",
size: "1200x800",
metadata: {
location: "Mountain Lake",
camera: "Canon EOS 5D Mark IV",
keywords: ["scenery", "mountain", "lake"],
exifData: [
{
name: "Exposure Time",
value: "1/659 sec."
},
{
name: "FNumber",
value: "f/4.0"
},
{
name: "ISO",
value: "100"
}
]
}
};
+88
View File
@@ -0,0 +1,88 @@
import * as path from 'path'
import * as pMap from 'p-map'
import { CONFIG_DEFAULT } from '@plastichub/osr-cli-commons'
import { resolve } from '@plastichub/osr-cli-commons/fs'
import { files } from '@plastichub/osr-cli-commons/glob'
import { sync as cp } from '@plastichub/fs/copy'
import { sync as exists } from '@plastichub/fs/exists'
import { sync as rm } from '@plastichub/fs/remove'
import { sync as read } from '@plastichub/fs/read'
import { sync as write } from '@plastichub/fs/write'
import { logger as _logger } from '@plastichub/core/debug'
import * as ExifReader from 'exifreader'
import { Logger, TTransportLogger, ILogObject } from 'tslog'
import { createStream } from "rotating-file-stream"
import { sanitize } from '@plastichub/osr-i18n/_cli'
import { translateText } from '@plastichub/osr-i18n/lib/translate'
import { IOptions as IOptionsI18n } from '@plastichub/osr-i18n/types'
import { translate } from '@plastichub/osr-i18n/lib/translate'
import { resize, getResizePatterns, format, getFormats, meta } from '@plastichub/osr-media/lib/media/images'
import {
ENABLED_PRODUCTS, I18N_SOURCE_LANGUAGE, I18N_STORE,
LANGUAGES,
OSRL_ENV, OSRL_LANG_FLAVOR,
OSRL_MODULE_NAME, OSR_ROOT, PRODUCTS_TARGET_SRC, PRODUCT_CONFIG, PRODUCT_HUGO_TEMPLATE, OSRL_PRODUCT_PROFILE, PRODUCT_ROOT, PRODUCT_DIR,
OSRL_ENVIRONMENT,
RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS,
TASK_CONFIG_LOG_DIRECTORY
} from './config'
import { GalleryImage } from './images'
const debug = false
const verbose = true
const logger = _logger('ph-site')
const IMAGES_GLOB = '*.+(JPG|jpg|png|PNG|gif)'
const _convertProductMedia = true
const _translateProductAssets = true
const _populateProductDefaults = true
export const createSubLogger = (root: Logger, level: string, name: string): Logger => {
const ret = root.getChildLogger({
name,
type: "pretty",
displayInstanceName: true,
displayFilePath: 'hidden',
instanceName: name,
displayFunctionName: true,
displayRequestId: true,
displayLogLevel: true,
colorizePrettyLogs: true,
hostname: "osr-cli",
displayLoggerName: true,
displayTypes: false,
prefix: ["\n\t "],
})
const logFile = path.resolve(resolve(`${TASK_CONFIG_LOG_DIRECTORY}/${name}.log`))
const stream = createStream(logFile,
{
size: "10M", // rotate every 10 MegaBytes written
interval: "1d", // rotate daily
compress: "gzip", // compress rotated files
});
const transport = {
minLevel: level,
transportLogger: {
info: (logObject: ILogObject) => {
stream.write(JSON.stringify(logObject, null, 2) + "\n");
}
}
}
logger.attachTransport(transport.transportLogger as any, level as any)
return logger
}
export const writeTaskConfig = (taskName, config: any) => {
const file = path.resolve(resolve(`${TASK_CONFIG_LOG_DIRECTORY}/${taskName}.json`))
write(file, JSON.stringify(config, null, 2))
}
+59
View File
@@ -0,0 +1,59 @@
export const item = (rel) => `machines/${rel}`
export const injectors = [
item('injection/elena'),
item('injection/elena-xmax'),
item('injection/lever'),
item('injection/lever-laser-cut'),
item('injection/myriad'),
item('injection/components/304_Valve-40mm')
]
export const shredders = [
item('shredder/pp-v2.1'),
item('shredder/pp-v3.3'),
item('shredder/asterix-pp'),
item('shredder/asterix-sm-morren'),
item('shredder/pp-v4'),
item('shredder/pp-v42'),
item('shredder/obelix'),
item('shredder/idefix'),
item('shredder/components/shredder_v31-light'),
item('shredder/bicycle-shredder'),
item('zoe')
]
export const extruders = [
item('extrusion/lydia-mini'),
item('extrusion/lydia-v3.5'),
item('extrusion/lydia-v4.5'),
item('extrusion/pp-v3'),
item('extrusion/pp-v4-extruder'),
item('extrusion/pp-v42-extruder'),
item('extrusion/lydia-print-head'),
item('extrusion/components/202_FilamentHousing'),
item('extrusion/components/102_lucy-mini')
]
export const sheetpresses = [
item('sheetpress/100cm'),
item('sheetpress/110cm-fiction-factory'),
item('sheetpress/150cm'),
item('sheetpress/cassandra'),
item('sheetpress/cassandra-light'),
item('sheetpress/sheetpress-cell')
]
export const test_items = [
//item('sheetpress/100cm'),
// item('sheetpress/110cm-fiction-factory')
// item('extrusion/plastic-odyssey'),
item('injection/elena'),
//item('injection/lever')
//'products/zoe',
// 'products/sheetpress/cassandra-light',
// 'products/sheetpress/sheetpress-cell',
//'products/components/700_UniversalHousing'
//'products/shredder/components/shredder_v33',
//'products/shredder/asterix-sm'
];
+164
View File
@@ -0,0 +1,164 @@
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'
import * as path from 'path'
import * as pMap from 'p-map'
import { resolve } from '@plastichub/osr-cli-commons/fs'
import { files } from '@plastichub/osr-cli-commons/glob'
import { sync as exists } from '@plastichub/fs/exists'
import { sync as read } from '@plastichub/fs/read'
import { logger as _logger } from '@plastichub/core/debug'
import * as ExifReader from 'exifreader'
import { translateText } from '@plastichub/osr-i18n/lib/translate'
import { resize, getResizePatterns, format, getFormats, meta } from '@plastichub/osr-media/lib/media/images'
import {
I18N_STORE,
OSR_ROOT, PRODUCT_CONFIG, PRODUCT_ROOT,
RETAIL_MEDIA_CACHE
} from './config'
import { GalleryImage } from './images'
const logger = _logger('ph-site')
const IMAGES_GLOB = '*.+(JPG|jpg|png|PNG|gif)'
export const productGallery = async (grunt, assetPath, product, lang, dstLanguage): Promise<GalleryImage[]> => {
product = '' + product
const root = resolve(PRODUCT_ROOT())
const productConfig: any = read(PRODUCT_CONFIG(product), "json")
if (!productConfig) {
logger.error('Product config not found !' + product)
return
}
const mediaPath = `${root}/${product}/${assetPath}/`
if (!exists(mediaPath)) {
return []
}
const galleryFiles = files(mediaPath, IMAGES_GLOB, {
cwd: mediaPath,
absolute: false
})
if (!galleryFiles) {
return
}
const removeBufferValues = (obj: any): any => {
for (const key in obj) {
const val = obj[key]
if (Buffer.isBuffer(val)) {
}
if (Buffer.isBuffer(val)) {
delete obj[key];
} else if (typeof val === 'object') {
removeBufferValues(val);
}
}
return obj;
}
const removeArrayValues = (obj: any): any => {
for (const key in obj) {
if (key == 'id') {
delete obj[key]
}
if (Array.isArray(obj[key]) || Buffer.isBuffer(obj[key])) {
delete obj[key];
} else if (typeof obj[key] === 'object') {
removeArrayValues(obj[key]);
}
}
return obj;
}
const removeEmptyObjects = (obj: any): any => {
for (const key in obj) {
if (typeof obj[key] === 'object' ||
(key == 'value' && typeof obj[key] === 'number' && obj[key] === 0 ||
key == 'base64')
) {
obj[key] = removeEmptyObjects(obj[key]);
if (Object.keys(obj[key]).length === 0) {
delete obj[key];
}
}
}
return obj;
}
const removeArrays = (obj: any): any => {
for (const key in obj) {
if (key == 'description' && typeof obj[key] === 'string' && obj[key].split(',').length > 2) {
try {
if (Buffer.isBuffer(Buffer.from(obj[key].split(','))))
delete obj[key]
} catch (e) {
}
} else if (typeof obj[key] === 'object') {
removeArrays(obj[key]);
}
}
return obj;
}
return await pMap(galleryFiles, async (file: string) => {
const parts = path.parse(file)
const filePath = path.join(mediaPath, file)
let imageMeta: any = await meta(filePath)
const exifRaw: any = await ExifReader.load(filePath)
const title = exifRaw?.title?.description || ''
const keywords = exifRaw?.['LastKeywordXMP']?.description || exifRaw?.iptc?.Keywords?.description || ''
const exifDescription = exifRaw?.['ImageDescription']?.description || ''
const width = exifRaw?.['Image Width']?.value
const height = exifRaw?.['Image Height']?.value
const lon = exifRaw?.['GPSLongitude']?.description
const lat = exifRaw?.['GPSLatitude']?.description
const description = exifDescription || exifRaw?.iptc?.['Caption/Abstract'].description || ''
imageMeta.exif = exifRaw
imageMeta = removeBufferValues(imageMeta)
imageMeta = removeArrayValues(imageMeta)
imageMeta = removeArrays(imageMeta)
imageMeta = removeEmptyObjects(imageMeta)
delete imageMeta.xmp
delete imageMeta.icc
delete imageMeta.exif.icc
delete imageMeta.exif.xmp
delete imageMeta.exif.iptc
const keywordsTranslated = await translateText(keywords || '', lang, dstLanguage,{
store: I18N_STORE(OSR_ROOT(), dstLanguage)
})
const assetUrl = (filePath) => `[[OSR_MACHINES_ASSETS_URL]]/[[product_relative]]/${assetPath}/${filePath}`
const ret: GalleryImage =
{
name: path.parse(file).name,
url: assetUrl(file),
thumb: assetUrl(`/20/webp/${parts.name}.webp`),
responsive: assetUrl(`/webp/${parts.name}.webp`),
meta: imageMeta || "",
keywords: keywords.split(',').map((k) => k.trim()),
description,
alt: `${description} - ${keywordsTranslated || ''}`,
width,
height,
title
}
return ret
})
}
export const compileProductAssets = async (grunt, product_root, srcLang, dstLanguage) => {
logger.info('Resize Product Media Assets ', product_root);
await resize(getResizePatterns(product_root, 'drawings'))
await format(getFormats(product_root, 'drawings'), { png: false, cache: RETAIL_MEDIA_CACHE })
await resize(getResizePatterns(product_root, 'renderings'))
await format(getFormats(product_root, 'renderings'), { png: false, cache: RETAIL_MEDIA_CACHE })
await resize(getResizePatterns(product_root, 'renderings/20'))
await format(getFormats(product_root, 'renderings/20'), { png: false, cache: RETAIL_MEDIA_CACHE })
await resize(getResizePatterns(product_root, 'media/gallery'))
await format(getFormats(product_root, 'media/gallery'), { png: false, cache: RETAIL_MEDIA_CACHE })
await resize(getResizePatterns(product_root, 'media/gallery/20'))
await format(getFormats(product_root, 'media/gallery/20'), { png: false, cache: RETAIL_MEDIA_CACHE })
}
+229
View File
@@ -0,0 +1,229 @@
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'
import * as path from 'path'
import { CONFIG_DEFAULT } from '@plastichub/osr-cli-commons'
import { resolve } from '@plastichub/osr-cli-commons/fs'
import { sync as exists } from '@plastichub/fs/exists'
import { sync as read } from '@plastichub/fs/read'
import { sync as write } from '@plastichub/fs/write'
import { logger as _logger } from '@plastichub/core/debug'
import { sanitize } from '@plastichub/osr-i18n/_cli'
import { IOptions as IOptionsI18n } from '@plastichub/osr-i18n/types'
import { translate } from '@plastichub/osr-i18n/lib/translate'
import { compileProductAssets, productGallery } from './media'
import { writeTaskConfig } from './log'
import {
ENABLED_PRODUCTS, I18N_SOURCE_LANGUAGE, I18N_STORE,
LANGUAGES,
OSRL_ENV, OSRL_LANG_FLAVOR,
OSRL_MODULE_NAME, OSR_ROOT, PRODUCTS_TARGET_SRC, PRODUCT_CONFIG, PRODUCT_HUGO_TEMPLATE, OSRL_PRODUCT_PROFILE, PRODUCT_ROOT, PRODUCT_DIR,
OSRL_ENVIRONMENT,
RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS,
TranslateProductAssets,
ConvertProductMedia,
PopulateProductDefaults,
RETAIL_COMPILE_CACHE
} from './config'
import { ICompileTaskOptions } from '@plastichub/osr-tasks/tasks/compile'
const logger = _logger('ph-site')
/////////////////////////////////////////////////////////////////////
//
// Product Multi Task products[product] -> content/retail/product
export const populateProductDefaults = async (grunt, product_root, srcLang, dstLanguage) => {
const _createFile = (file: string) => {
file = path.resolve(path.join(product_root, file))
if (!exists(file)) {
write(file, '')
}
}
_createFile('templates/shared/product_features.md')
}
export const compileProductTask = async (grunt, product, lang, dstLanguage, target, options: any = {}) => {
const config = {}
product = '' + product
const logLevel = grunt.option('logLevel') || 'warn'
const product_rel = product.replace('products/', '')
const root = resolve(PRODUCT_ROOT())
const slug = path.parse(product).base
const debug = grunt.option('debug')
const productConfig: any = read(PRODUCT_CONFIG(product), "json")
if (!productConfig) {
logger.error('Product config not found !' + product)
return
}
productConfig.description = productConfig.description || ''
if (!productConfig) {
logger.error('Product config not found !' + product)
return
}
const translateProductAssets = async () => {
logger.info('Translate Product Assets ' + slug + ' ' + dstLanguage + ' ' + product_rel)
const config: any = CONFIG_DEFAULT()
if (dstLanguage === I18N_SOURCE_LANGUAGE) {
return
}
//const _logger = createSubLogger(logger, logLevel, 'i18n')
logger.setSettings({ minLevel: RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS })
const i18nOptions: IOptionsI18n = {
srcLang: I18N_SOURCE_LANGUAGE,
dstLang: dstLanguage,
src: `${PRODUCT_DIR(product)}/specs.xlsx`,
store: I18N_STORE(OSR_ROOT(), dstLanguage),
dst: "${SRC_DIR}/${SRC_NAME}-${DST_LANG}${SRC_EXT}",
query: "$[*][0,1]",
cache: true,
api_key: config.deepl.auth_key,
logLevel: RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS
}
const ret = await translate(sanitize(i18nOptions) as any)
return ret
}
const onCompiled = async (src, dst, options, content) => {
// translate specs
if (TranslateProductAssets || grunt.option('translateProductAssets')) {
await translateProductAssets()
}
if (ConvertProductMedia || grunt.option('convertProductMedia')) {
await compileProductAssets(grunt, PRODUCT_DIR(product), lang, dstLanguage)
}
if (PopulateProductDefaults) {
await populateProductDefaults(grunt, PRODUCT_DIR(product), lang, dstLanguage)
}
// logger.warn('On Compiled Product ' + product + ' ' + dstLanguage)
}
let defaultOptions: any = {
src: [path.resolve(path.join(root, PRODUCT_HUGO_TEMPLATE))],
options: {
cache: RETAIL_COMPILE_CACHE,
debug,
env: OSRL_ENVIRONMENT,
format: 'html',
language: OSRL_LANG_FLAVOR,
module: OSRL_MODULE_NAME,
output: `${target}/${product_rel}/_index.md`,
profile: OSRL_PRODUCT_PROFILE,
root,
cwd: root,
watchContent: false,
logLevel,
store: I18N_STORE(OSR_ROOT(), dstLanguage),
sourceLanguage: I18N_SOURCE_LANGUAGE,
targetLanguage: dstLanguage,
variables: {
root,
product,
product_rel,
product_relative: '' + product_rel,
sourceLanguage: I18N_SOURCE_LANGUAGE,
targetLanguage: dstLanguage,
language: dstLanguage,
i18n: I18N_STORE(OSR_ROOT(), dstLanguage),
...productConfig,
fm: {
keywords: (productConfig.keywords || "").split(',').map((k) => k.trim())
}
},
...options,
onCompileDone: onCompiled,
onCompile: async (options) => {
const gallery = await productGallery(grunt, 'media/gallery', product, lang, dstLanguage)
const galleryRenderings = await productGallery(grunt, 'renderings', product, lang, dstLanguage)
options.variables.fm.rGallery = gallery
options.variables.fm.rGalleryRenderings = galleryRenderings
return options
}
} as ICompileTaskOptions
}
debug && logger.debug('Create product compile options for ' + product, defaultOptions.options)
config[`content-${dstLanguage}-${slug}`] = {
...defaultOptions,
}
grunt.extendConfig({ compile: config })
grunt.registerTask(`content-${dstLanguage}-${slug}`, `compile:content-${dstLanguage}-${slug}`)
}
export const registerProductTasks = (grunt) => {
const logLevel = grunt.option('logLevel') || 'warn'
logger.setSettings({ minLevel: logLevel })
const product_compile_tasks = []
const productTasks = (items) => {
items.forEach((product) => {
const slug = path.parse(product).base
compileProductTask(grunt, product, I18N_SOURCE_LANGUAGE, I18N_SOURCE_LANGUAGE, "./content/en/retail")
product_compile_tasks.push(`compile:content-en-${slug}`)
LANGUAGES.forEach((lang) => {
product_compile_tasks.push(`compile:content-${lang}-${slug}`)
compileProductTask(grunt, product, I18N_SOURCE_LANGUAGE, lang, `./content/${lang}/retail`)
})
})
}
productTasks(readProducts(grunt.option('branch') || 'test'))
grunt.registerTask(`content-all`, product_compile_tasks)
}
/////////////////////////////////////////////////////////////////////
//
// Product Single Test Task products[product] -> src/retail/product
export const productContentOptions = (target, product) => {
product = '' + product
const product_rel = product.replace('products/', '')
const root = resolve(PRODUCT_ROOT())
const productConfig: any = read(PRODUCT_CONFIG(product), "json")
if (!productConfig) {
logger.error('Product config not found !' + product)
}
return {
debug: false,
watch: false,
root,
cwd: root,
env: OSRL_ENV,
profile: '${root}/.osrl.json',
output: `${target}/${product_rel}/_index.md`,
// format: 'html',
module: OSRL_MODULE_NAME,
cache: true,
variables: {
product,
product_rel,
root,
product_relative: '' + product_rel,
...productConfig
}
}
}
export const productHugoTask = (grunt, product, options: any = {}, product_item_tasks) => {
if (!product) {
logger.error('Invalid product')
}
const config = {}
const slug = path.parse(product).base
const target = path.resolve(PRODUCTS_TARGET_SRC)
const productOptions = productContentOptions(target, product)
config[`product-${slug}`] = {
src: [PRODUCT_HUGO_TEMPLATE],
options: productOptions
}
grunt.extendConfig({
compile: config
})
grunt.registerTask(`product-${slug}`, `compile:product-${slug}`)
product_item_tasks.push(`compile:product-${slug}`)
grunt.registerTask(`products-hugo`, product_item_tasks)
writeTaskConfig(`compile_product-${slug}`, config)
}
export const readProducts = (branch: string = 'test') => {
const conf = read(ENABLED_PRODUCTS, "json") || {}
if (branch) {
conf['all'] = [...conf["sheetpress"], ...conf["injectors"], ...conf["extruders"], ...conf["shredders"]]
return conf[branch] || []
} else {
return Object.values(conf).flat()
}
}