core-maugli 1.2.60 → 1.2.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/astro-image-resize.mjs +25 -0
- package/astro.config.mjs +160 -0
- package/package.json +7 -1
- package/resize-all.cjs +79 -0
- package/scripts/check-version.js +23 -3
- package/src/i18n/ru.json +1 -1
- package/src/utils/image-utils.ts +13 -27
- package/tsconfig.json +8 -0
- package/typograf-batch.js +49 -0
- package/vite.config.js +11 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// astro-image-resize.mjs - Astro integration for image processing and previews
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
|
|
4
|
+
export default function imageResize() {
|
|
5
|
+
return {
|
|
6
|
+
name: 'image-resize',
|
|
7
|
+
hooks: {
|
|
8
|
+
'astro:build:start': () => {
|
|
9
|
+
console.log('🖼️ Starting image processing for build...');
|
|
10
|
+
try {
|
|
11
|
+
execSync('node scripts/resize-for-build.cjs', { stdio: 'inherit' });
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error('❌ Error during image resizing:', error.message);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log('🎭 Starting preview generation for build...');
|
|
17
|
+
try {
|
|
18
|
+
execSync('BUILD_MODE=1 node scripts/generate-previews.js', { stdio: 'inherit' });
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('❌ Error during preview generation:', error.message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
package/astro.config.mjs
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import mdx from '@astrojs/mdx';
|
|
2
|
+
import sitemap from '@astrojs/sitemap';
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
4
|
+
import { defineConfig } from 'astro/config';
|
|
5
|
+
import remarkSlug from 'remark-slug';
|
|
6
|
+
import { imagetools } from 'vite-imagetools';
|
|
7
|
+
import { VitePWA } from 'vite-plugin-pwa';
|
|
8
|
+
import imageResize from './astro-image-resize.mjs';
|
|
9
|
+
import { maugliConfig } from './src/config/maugli.config';
|
|
10
|
+
import siteConfig from './src/data/site-config';
|
|
11
|
+
import customSlugify from './src/utils/remark-slugify';
|
|
12
|
+
|
|
13
|
+
export const pwaOptions = {
|
|
14
|
+
registerType: 'autoUpdate',
|
|
15
|
+
includeAssets: ['favicon.svg', 'favicon.ico', 'robots.txt', 'apple-touch-icon.png'],
|
|
16
|
+
manifest: {
|
|
17
|
+
name: "Maugli Blog",
|
|
18
|
+
short_name: "Maugli",
|
|
19
|
+
start_url: "/",
|
|
20
|
+
display: "standalone",
|
|
21
|
+
background_color: maugliConfig.pwa?.backgroundColor ?? '#ffffff',
|
|
22
|
+
theme_color: maugliConfig.pwa?.themeColor ?? '#0cbf11',
|
|
23
|
+
icons: maugliConfig.pwa?.icons ?? [
|
|
24
|
+
{
|
|
25
|
+
src: "/icon-192.png",
|
|
26
|
+
sizes: "192x192",
|
|
27
|
+
type: "image/png",
|
|
28
|
+
purpose: "any maskable",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
src: "/icon-512.png",
|
|
32
|
+
sizes: "512x512",
|
|
33
|
+
type: "image/png",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
workbox: {
|
|
38
|
+
navigateFallback: '/index.html',
|
|
39
|
+
cleanupOutdatedCaches: true,
|
|
40
|
+
navigateFallbackDenylist: [/^\/api\//],
|
|
41
|
+
clientsClaim: true,
|
|
42
|
+
globPatterns: ['**/*.{js,css,html,png,jpg,jpeg,webp,svg}'],
|
|
43
|
+
runtimeCaching: [
|
|
44
|
+
{
|
|
45
|
+
urlPattern: ({ request }) => request.destination === 'image',
|
|
46
|
+
handler: 'CacheFirst',
|
|
47
|
+
options: {
|
|
48
|
+
cacheName: 'images-cache',
|
|
49
|
+
expiration: {
|
|
50
|
+
maxEntries: 50,
|
|
51
|
+
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 дней
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
urlPattern: ({ request }) => request.destination === 'font',
|
|
57
|
+
handler: 'CacheFirst',
|
|
58
|
+
options: {
|
|
59
|
+
cacheName: 'fonts-cache',
|
|
60
|
+
expiration: {
|
|
61
|
+
maxEntries: 20,
|
|
62
|
+
maxAgeSeconds: 365 * 24 * 60 * 60 // 1 год
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
devOptions: {
|
|
69
|
+
enabled: true, // чтобы работал в деве
|
|
70
|
+
type: 'module',
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// https://astro.build/config
|
|
75
|
+
export default defineConfig({
|
|
76
|
+
site: siteConfig.website,
|
|
77
|
+
image: {
|
|
78
|
+
service: {
|
|
79
|
+
entrypoint: 'astro/assets/services/sharp',
|
|
80
|
+
config: {
|
|
81
|
+
limitInputPixels: false,
|
|
82
|
+
// Aggressive optimization for better performance
|
|
83
|
+
jpeg: { quality: 75, progressive: true },
|
|
84
|
+
webp: { quality: 75, effort: 6 },
|
|
85
|
+
avif: { quality: 65, effort: 6 },
|
|
86
|
+
png: { quality: 75, compressionLevel: 9 },
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
integrations: [
|
|
91
|
+
mdx(),
|
|
92
|
+
sitemap(),
|
|
93
|
+
imageResize()
|
|
94
|
+
],
|
|
95
|
+
vite: {
|
|
96
|
+
plugins: [
|
|
97
|
+
tailwindcss(),
|
|
98
|
+
imagetools({
|
|
99
|
+
// Aggressive image optimization
|
|
100
|
+
defaultDirectives: () => {
|
|
101
|
+
return new URLSearchParams({
|
|
102
|
+
format: 'webp',
|
|
103
|
+
quality: '75',
|
|
104
|
+
progressive: 'true',
|
|
105
|
+
// Enable compression
|
|
106
|
+
effort: '6'
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
// Additional formats for fallback
|
|
110
|
+
formats: ['webp', 'avif'],
|
|
111
|
+
// Disable for development to speed up build
|
|
112
|
+
disabled: process.env.NODE_ENV === 'development'
|
|
113
|
+
}),
|
|
114
|
+
VitePWA(pwaOptions)
|
|
115
|
+
],
|
|
116
|
+
build: {
|
|
117
|
+
cssCodeSplit: true,
|
|
118
|
+
minify: 'esbuild',
|
|
119
|
+
target: 'es2020',
|
|
120
|
+
rollupOptions: {
|
|
121
|
+
output: {
|
|
122
|
+
// Separate CSS chunks for better caching
|
|
123
|
+
assetFileNames: (assetInfo) => {
|
|
124
|
+
if (assetInfo.name && assetInfo.name.endsWith('.css')) {
|
|
125
|
+
return 'assets/css/[name].[hash][extname]';
|
|
126
|
+
}
|
|
127
|
+
if (assetInfo.name && /\.(png|jpe?g|svg|gif|webp|avif)$/.test(assetInfo.name)) {
|
|
128
|
+
return 'assets/img/[name].[hash][extname]';
|
|
129
|
+
}
|
|
130
|
+
return 'assets/[name].[hash][extname]';
|
|
131
|
+
},
|
|
132
|
+
chunkFileNames: 'assets/js/[name].[hash].js',
|
|
133
|
+
manualChunks: {
|
|
134
|
+
// Split vendor code for better caching - removed astro from manual chunks
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
// Remove problematic external configuration
|
|
138
|
+
},
|
|
139
|
+
// Additional optimization settings
|
|
140
|
+
reportCompressedSize: false, // Faster build
|
|
141
|
+
chunkSizeWarningLimit: 1000
|
|
142
|
+
},
|
|
143
|
+
css: {
|
|
144
|
+
// Optimize CSS processing
|
|
145
|
+
preprocessorOptions: {
|
|
146
|
+
scss: {
|
|
147
|
+
// Additional SCSS options if needed
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
optimizeDeps: {
|
|
152
|
+
// Improve dev performance - astro should not be excluded
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
markdown: {
|
|
156
|
+
remarkPlugins: [
|
|
157
|
+
[remarkSlug, { slug: customSlugify }]
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "core-maugli",
|
|
3
3
|
"description": "Astro & Tailwind CSS blog theme for Maugli.",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "1.2.
|
|
5
|
+
"version": "1.2.62",
|
|
6
6
|
"license": "GPL-3.0-or-later OR Commercial",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -76,6 +76,9 @@
|
|
|
76
76
|
},
|
|
77
77
|
"files": [
|
|
78
78
|
"src",
|
|
79
|
+
"resize-all.cjs",
|
|
80
|
+
"astro-image-resize.mjs",
|
|
81
|
+
"typograf-batch.js",
|
|
79
82
|
"public/favicon.svg",
|
|
80
83
|
"public/icon-192.png",
|
|
81
84
|
"public/icon-512.png",
|
|
@@ -89,6 +92,9 @@
|
|
|
89
92
|
"public/blackbox*.webp",
|
|
90
93
|
"scripts",
|
|
91
94
|
"bin",
|
|
95
|
+
"astro.config.mjs",
|
|
96
|
+
"tsconfig.json",
|
|
97
|
+
"vite.config.js",
|
|
92
98
|
".gitignore",
|
|
93
99
|
"netlify.toml"
|
|
94
100
|
],
|
package/resize-all.cjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// resize-all.cjs - рекурсивный ресайз изображений
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const sharp = require('sharp');
|
|
5
|
+
|
|
6
|
+
// Размеры для генерации
|
|
7
|
+
const sizes = [400, 800, 1200];
|
|
8
|
+
|
|
9
|
+
const inputDir = './public';
|
|
10
|
+
const processedFiles = new Set(); // Отслеживаем обработанные файлы
|
|
11
|
+
|
|
12
|
+
// Рекурсивная функция для обхода папок
|
|
13
|
+
function processDirectory(dir) {
|
|
14
|
+
if (!fs.existsSync(dir)) {
|
|
15
|
+
console.log(`Папка ${dir} не существует`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const items = fs.readdirSync(dir);
|
|
20
|
+
|
|
21
|
+
items.forEach(item => {
|
|
22
|
+
const itemPath = path.join(dir, item);
|
|
23
|
+
const stat = fs.statSync(itemPath);
|
|
24
|
+
|
|
25
|
+
if (stat.isDirectory()) {
|
|
26
|
+
// Рекурсивно обрабатываем подпапки
|
|
27
|
+
processDirectory(itemPath);
|
|
28
|
+
} else if (stat.isFile()) {
|
|
29
|
+
const ext = path.extname(item).toLowerCase();
|
|
30
|
+
const baseName = path.basename(item, ext);
|
|
31
|
+
|
|
32
|
+
// Проверяем, что это изображение и не содержит размер в названии
|
|
33
|
+
if (['.jpg', '.jpeg', '.png', '.webp'].includes(ext)) {
|
|
34
|
+
// Исключаем PWA иконки и служебные файлы
|
|
35
|
+
const excludePatterns = [
|
|
36
|
+
'icon-192', 'icon-512', // PWA иконки
|
|
37
|
+
'favicon', // Фавиконки
|
|
38
|
+
'logo', // Логотипы (часто SVG, но на всякий случай)
|
|
39
|
+
'manifest' // Файлы манифеста
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const shouldExclude = excludePatterns.some(pattern => baseName.includes(pattern));
|
|
43
|
+
|
|
44
|
+
// Пропускаем файлы, которые уже содержат размер (например, image-400.webp, image-800-800.webp)
|
|
45
|
+
// Улучшенная проверка: пропускаем файлы с -400, -800, -1200 в любом месте названия
|
|
46
|
+
const hasResizeSuffix = sizes.some(size => baseName.includes(`-${size}`));
|
|
47
|
+
|
|
48
|
+
if (!hasResizeSuffix && !shouldExclude && !processedFiles.has(itemPath)) {
|
|
49
|
+
processedFiles.add(itemPath);
|
|
50
|
+
|
|
51
|
+
console.log(`Обрабатываем: ${itemPath}`);
|
|
52
|
+
|
|
53
|
+
sizes.forEach(width => {
|
|
54
|
+
const outputPath = path.join(path.dirname(itemPath), `${baseName}-${width}${ext}`);
|
|
55
|
+
|
|
56
|
+
// Проверяем, что файл еще не существует
|
|
57
|
+
if (!fs.existsSync(outputPath)) {
|
|
58
|
+
sharp(itemPath)
|
|
59
|
+
.resize(width)
|
|
60
|
+
.toFile(outputPath, (err) => {
|
|
61
|
+
if (err) {
|
|
62
|
+
console.error(`Ошибка при создании ${outputPath}:`, err.message);
|
|
63
|
+
} else {
|
|
64
|
+
console.log(`✅ Создан: ${path.relative('./public', outputPath)}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
console.log(`⏭️ Пропускаем (уже существует): ${path.relative('./public', outputPath)}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log('🔄 Начинаем ресайз всех изображений...');
|
|
78
|
+
processDirectory(inputDir);
|
|
79
|
+
console.log('✅ Обработка завершена!');
|
package/scripts/check-version.js
CHANGED
|
@@ -38,20 +38,35 @@ async function getMaugliConfig() {
|
|
|
38
38
|
try {
|
|
39
39
|
const configPath = path.join(process.cwd(), 'src/config/maugli.config.ts');
|
|
40
40
|
if (!fs.existsSync(configPath)) {
|
|
41
|
+
console.log(colorize('⚠️ maugli.config.ts not found at src/config/maugli.config.ts', 'yellow'));
|
|
41
42
|
return null;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// Простое чтение конфига через регулярные выражения
|
|
45
46
|
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
46
|
-
|
|
47
|
+
console.log(colorize('🔍 Reading maugli.config.ts...', 'cyan'));
|
|
48
|
+
|
|
49
|
+
// Ищем forceUpdate в automation секции
|
|
50
|
+
const automationMatch = configContent.match(/automation\s*:\s*{([^}]+)}/s);
|
|
51
|
+
if (!automationMatch) {
|
|
52
|
+
console.log(colorize('⚠️ automation section not found in config', 'yellow'));
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const automationSection = automationMatch[1];
|
|
57
|
+
const forceUpdateMatch = automationSection.match(/forceUpdate\s*:\s*(true|false)/);
|
|
58
|
+
|
|
59
|
+
const forceUpdate = forceUpdateMatch ? forceUpdateMatch[1] === 'true' : false;
|
|
60
|
+
|
|
61
|
+
console.log(colorize(`📋 Config found - forceUpdate: ${forceUpdate}`, 'cyan'));
|
|
47
62
|
|
|
48
63
|
return {
|
|
49
64
|
automation: {
|
|
50
|
-
forceUpdate:
|
|
65
|
+
forceUpdate: forceUpdate
|
|
51
66
|
}
|
|
52
67
|
};
|
|
53
68
|
} catch (error) {
|
|
54
|
-
console.warn(colorize('⚠️ Could not read maugli.config.ts', 'yellow'));
|
|
69
|
+
console.warn(colorize('⚠️ Could not read maugli.config.ts: ' + error.message, 'yellow'));
|
|
55
70
|
return null;
|
|
56
71
|
}
|
|
57
72
|
}
|
|
@@ -257,6 +272,11 @@ async function main() {
|
|
|
257
272
|
// Check forceUpdate setting from maugli.config.ts
|
|
258
273
|
const forceUpdate = maugliConfig?.automation?.forceUpdate || false;
|
|
259
274
|
|
|
275
|
+
console.log(colorize(`\n🔧 Configuration check:`, 'cyan'));
|
|
276
|
+
console.log(colorize(` • maugli.config.ts found: ${maugliConfig ? 'Yes' : 'No'}`, 'white'));
|
|
277
|
+
console.log(colorize(` • forceUpdate setting: ${forceUpdate}`, 'white'));
|
|
278
|
+
console.log(colorize(` • CI/CD detected: ${isCI}`, 'white'));
|
|
279
|
+
|
|
260
280
|
if (isCI) {
|
|
261
281
|
console.log(colorize('\n🤖 CI/CD environment detected. Updating automatically...', 'cyan'));
|
|
262
282
|
const success = await performUpdate();
|
package/src/i18n/ru.json
CHANGED
package/src/utils/image-utils.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/utils/image-utils.ts - Утилиты для работы с изображениями
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Определяет правильный путь к изображению
|
|
5
|
-
* Все пользовательские изображения
|
|
6
|
-
*
|
|
4
|
+
* Определяет правильный путь к изображению
|
|
5
|
+
* Все пользовательские изображения находятся в /img/ и подпапках
|
|
6
|
+
* Автоматическая оптимизация применяется ко всем изображениям в public/img/
|
|
7
7
|
*/
|
|
8
8
|
export function getImagePath(imageName: string, contentType?: 'blog' | 'author' | 'product' | 'project' | 'tag'): string {
|
|
9
9
|
// Если путь уже абсолютный (начинается с /), возвращаем как есть
|
|
@@ -11,30 +11,26 @@ export function getImagePath(imageName: string, contentType?: 'blog' | 'author'
|
|
|
11
11
|
return imageName;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
return `/img/${imageName}`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Все остальные изображения ищем в page-images
|
|
20
|
-
return `/img/page-images/${imageName}`;
|
|
14
|
+
// Все изображения идут в /img/
|
|
15
|
+
return `/img/${imageName}`;
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
/**
|
|
24
19
|
* Получает путь к изображению для конкретного типа контента
|
|
25
|
-
*
|
|
20
|
+
* Все файлы просто в /img/ без подпапок
|
|
26
21
|
*/
|
|
27
22
|
export function getContentImagePath(slug: string, contentType: 'blog' | 'author' | 'product' | 'project' | 'tag', extension: string = '.webp'): string {
|
|
28
23
|
const fileName = `${contentType}_${slug}${extension}`;
|
|
29
|
-
return
|
|
24
|
+
return `/img/${fileName}`;
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
/**
|
|
33
28
|
* Получает путь к превью изображению
|
|
29
|
+
* Все файлы просто в /img/ без подпапок
|
|
34
30
|
*/
|
|
35
31
|
export function getPreviewImagePath(slug: string, contentType: 'blog' | 'author' | 'product' | 'project' | 'tag', extension: string = '.webp'): string {
|
|
36
32
|
const fileName = `previews_${contentType}_${slug}${extension}`;
|
|
37
|
-
return
|
|
33
|
+
return `/img/${fileName}`;
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
/**
|
|
@@ -54,22 +50,12 @@ export function getDefaultImagePath(contentType: 'blog' | 'author' | 'product' |
|
|
|
54
50
|
|
|
55
51
|
/**
|
|
56
52
|
* Конвертирует старые пути к новой структуре
|
|
57
|
-
*
|
|
53
|
+
* Все изображения теперь просто в /img/ и подпапках
|
|
58
54
|
*/
|
|
59
55
|
export function convertLegacyImagePath(imagePath: string): string {
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
{ from: '/img/authors/', to: '/img/page-images/' },
|
|
64
|
-
{ from: '/img/products/', to: '/img/page-images/' },
|
|
65
|
-
{ from: '/img/projects/', to: '/img/page-images/' },
|
|
66
|
-
{ from: '/img/uploads/', to: '/img/page-images/' }
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
for (const conversion of conversions) {
|
|
70
|
-
if (imagePath.startsWith(conversion.from)) {
|
|
71
|
-
return imagePath.replace(conversion.from, conversion.to);
|
|
72
|
-
}
|
|
56
|
+
// Убираем page-images из пути - теперь всё просто в /img/
|
|
57
|
+
if (imagePath.includes('/img/page-images/')) {
|
|
58
|
+
return imagePath.replace('/img/page-images/', '/img/');
|
|
73
59
|
}
|
|
74
60
|
|
|
75
61
|
return imagePath;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import Typograf from 'typograf';
|
|
2
|
+
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
|
|
5
|
+
const tp = new Typograf({ locale: ['ru', 'en-US'] });
|
|
6
|
+
const dir = './src/content/blog';
|
|
7
|
+
const cacheFile = './.typograf-cache.json';
|
|
8
|
+
|
|
9
|
+
let cache = {};
|
|
10
|
+
if (existsSync(cacheFile)) {
|
|
11
|
+
cache = JSON.parse(readFileSync(cacheFile, 'utf8'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let cacheUpdated = false;
|
|
15
|
+
|
|
16
|
+
readdirSync(dir)
|
|
17
|
+
.filter(f => f.endsWith('.md'))
|
|
18
|
+
.forEach(f => {
|
|
19
|
+
const file = dir + '/' + f;
|
|
20
|
+
const stats = statSync(file);
|
|
21
|
+
const mtime = stats.mtimeMs;
|
|
22
|
+
|
|
23
|
+
// Если не изменялся — скипаем
|
|
24
|
+
if (cache[f] === mtime) return;
|
|
25
|
+
|
|
26
|
+
const data = readFileSync(file, 'utf8');
|
|
27
|
+
const parts = data.split('---');
|
|
28
|
+
if (parts.length < 3) return;
|
|
29
|
+
let fm = yaml.load(parts[1]);
|
|
30
|
+
if (fm.title) fm.title = tp.execute(fm.title);
|
|
31
|
+
if (fm.description) fm.description = tp.execute(fm.description);
|
|
32
|
+
|
|
33
|
+
const newData = [
|
|
34
|
+
'---',
|
|
35
|
+
yaml.dump(fm).trim(),
|
|
36
|
+
'---',
|
|
37
|
+
tp.execute(parts.slice(2).join('---'))
|
|
38
|
+
].join('\n');
|
|
39
|
+
|
|
40
|
+
writeFileSync(file, newData);
|
|
41
|
+
cache[f] = mtime;
|
|
42
|
+
cacheUpdated = true;
|
|
43
|
+
console.log(`Typografed: ${file}`);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Сохраняем кеш только если были изменения
|
|
47
|
+
if (cacheUpdated) {
|
|
48
|
+
writeFileSync(cacheFile, JSON.stringify(cache, null, 2));
|
|
49
|
+
}
|
package/vite.config.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { imagetools } from 'vite-imagetools';
|
|
3
|
+
import { VitePWA } from 'vite-plugin-pwa';
|
|
4
|
+
import { pwaOptions } from './astro.config.mjs';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [
|
|
8
|
+
imagetools(),
|
|
9
|
+
VitePWA(pwaOptions)
|
|
10
|
+
]
|
|
11
|
+
});
|