core-maugli 1.2.3 → 1.2.5

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.
Files changed (28) hide show
  1. package/README.md +13 -0
  2. package/package.json +6 -13
  3. package/public/img/examples/blog/previews/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva.webp +0 -0
  4. package/public/img/examples/blog/previews/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese.webp +0 -0
  5. package/public/img/examples/blog/previews/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik.webp +0 -0
  6. package/public/img/examples/blog/previews/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte.webp +0 -0
  7. package/public/img/examples/blog/previews/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya.webp +0 -0
  8. package/public/img/examples/blog/previews/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga.webp +0 -0
  9. package/public/img/examples/blog/previews/post-agent-experience-mcp-biznes-v-epohu-ii-agentov.webp +0 -0
  10. package/public/img/examples/blog/previews/post_11.webp +0 -0
  11. package/public/img/examples/blog/previews/post_12.webp +0 -0
  12. package/public/img/examples/blog/previews/post_1_jsonld_guide.webp +0 -0
  13. package/public/img/examples/blog/previews/test-post.webp +0 -0
  14. package/public/img/examples/blog/previews/tr-post-1.webp +0 -0
  15. package/public/img/examples/products/previews/product_1.webp +0 -0
  16. package/public/img/examples/products/previews/product_2.webp +0 -0
  17. package/public/img/examples/projects/previews/project_1.webp +0 -0
  18. package/public/img/examples/projects/previews/project_2.webp +0 -0
  19. package/scripts/generate-previews.js +175 -0
  20. package/scripts/update-components.js +175 -0
  21. package/scripts/upgrade-config.js +23 -3
  22. package/src/components/LanguageSwitcher.astro +2 -16
  23. package/astro.config.mjs +0 -92
  24. package/bin/init.js +0 -201
  25. package/resize-all.cjs +0 -29
  26. package/tsconfig.json +0 -8
  27. package/typograf-batch.js +0 -49
  28. package/vite.config.js +0 -11
package/README.md CHANGED
@@ -87,6 +87,19 @@ Your blog will be available at `http://localhost:4321/`
87
87
  npm run build
88
88
  ```
89
89
 
90
+ ## Component Updates & Customization
91
+
92
+ **Important**: Maugli Blog is designed for centralized component updates. All components (`src/components/`, `src/layouts/`, `src/pages/`, etc.) are automatically updated to the latest version when you update the package with `npm install --save core-maugli@latest`. This ensures you always receive the latest features, bug fixes, and improvements.
93
+
94
+ This centralized update approach **does not affect**:
95
+
96
+ - Your content (`src/content/`)
97
+ - Your Maugli configuration (`src/config/maugli.config.ts`)
98
+ - Your custom styles (`src/styles/global.css` - preserved if customized)
99
+ - Your project settings (`package.json`, `astro.config.mjs`, etc.)
100
+
101
+ Only the core blog components are updated, while your customizations and settings remain intact.
102
+
90
103
  `npm run build` runs [`scripts/verify-assets.js`](scripts/verify-assets.js)
91
104
  before the Astro build. This script checks the SHA-256 hashes of the
92
105
  floating label component and footer badge to ensure they haven't been
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.3",
5
+ "version": "1.2.5",
6
6
  "license": "GPL-3.0-or-later OR Commercial",
7
7
  "repository": {
8
8
  "type": "git",
@@ -21,15 +21,17 @@
21
21
  "typograf": "node typograf-batch.js",
22
22
  "dev": "astro dev",
23
23
  "start": "astro dev",
24
- "build": "node typograf-batch.js && node scripts/verify-assets.js && astro build",
24
+ "build": "node typograf-batch.js && node scripts/generate-previews.js && node scripts/verify-assets.js && astro build",
25
25
  "test": "node tests/examplesFilter.test.ts",
26
26
  "astro": "astro",
27
27
  "featured:add": "node scripts/featured.js add",
28
28
  "featured:remove": "node scripts/featured.js remove",
29
29
  "featured:list": "node scripts/featured.js list",
30
30
  "upgrade": "node scripts/upgrade-config.js",
31
+ "update-components": "node scripts/update-components.js",
31
32
  "backup-update": "node scripts/update-with-backup.js",
32
- "postinstall": "node scripts/upgrade-config.js"
33
+ "postinstall": "node scripts/upgrade-config.js",
34
+ "generate-previews": "node scripts/generate-previews.js"
33
35
  },
34
36
  "dependencies": {
35
37
  "@astrojs/mdx": "^4.3.0",
@@ -61,17 +63,8 @@
61
63
  "files": [
62
64
  "src",
63
65
  "public",
64
- "scripts",
65
- "typograf-batch.js",
66
- "resize-all.cjs",
67
- "bin",
68
- "astro.config.mjs",
69
- "tsconfig.json",
70
- "vite.config.js",
71
- "LICENSE",
72
- "README.md"
66
+ "scripts"
73
67
  ],
74
- "main": "bin/index.js",
75
68
  "bin": {
76
69
  "core-maugli": "bin/index.js"
77
70
  }
@@ -0,0 +1,175 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import sharp from 'sharp';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ // Универсальное определение корня проекта
10
+ const rootDir = __dirname.includes('node_modules')
11
+ ? path.join(__dirname, '../../..')
12
+ : path.join(__dirname, '..');
13
+
14
+ const previewWidth = 400;
15
+ const previewHeight = 210;
16
+
17
+ // Функция для извлечения путей изображений из markdown файлов
18
+ function extractImagePaths() {
19
+ const imagePaths = new Set();
20
+ const contentDir = path.join(rootDir, 'src/content');
21
+
22
+ function scanDirectory(dir) {
23
+ if (!fs.existsSync(dir)) return;
24
+
25
+ const items = fs.readdirSync(dir);
26
+
27
+ for (const item of items) {
28
+ const itemPath = path.join(dir, item);
29
+ const stat = fs.statSync(itemPath);
30
+
31
+ if (stat.isDirectory()) {
32
+ scanDirectory(itemPath);
33
+ } else if (item.endsWith('.md') || item.endsWith('.mdx')) {
34
+ const content = fs.readFileSync(itemPath, 'utf-8');
35
+
36
+ // Извлекаем изображения из frontmatter (image: путь)
37
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
38
+ if (frontmatterMatch) {
39
+ const frontmatter = frontmatterMatch[1];
40
+ // Улучшенные регулярки для frontmatter
41
+ const imageMatches = frontmatter.match(/(?:^|\n)\s*(?:image|src):\s*['"]*([^'">\s\n]+)['"]*(?:\s|$)/g);
42
+ if (imageMatches) {
43
+ imageMatches.forEach(match => {
44
+ const imagePath = match.replace(/.*?(?:image|src):\s*['"]*/, '').replace(/['"]*\s*$/, '');
45
+ if (imagePath && !imagePath.startsWith('http') && imagePath.includes('/') && !imagePath.includes('authors/')) {
46
+ imagePaths.add(imagePath);
47
+ }
48
+ });
49
+ }
50
+ }
51
+
52
+ // Извлекаем изображения из содержимого markdown (![](путь) и <img src="путь">)
53
+ const imgMatches = content.match(/!\[.*?\]\(([^)]+)\)|<img[^>]+src\s*=\s*['"]*([^'">\s]+)['"]*[^>]*>/g);
54
+ if (imgMatches) {
55
+ imgMatches.forEach(match => {
56
+ let imagePath;
57
+ if (match.startsWith('![')) {
58
+ imagePath = match.match(/!\[.*?\]\(([^)]+)\)/)?.[1];
59
+ } else {
60
+ imagePath = match.match(/src\s*=\s*['"]*([^'">\s]+)['"]*/)?.[1];
61
+ }
62
+ if (imagePath && !imagePath.startsWith('http') && imagePath.includes('/') && !imagePath.includes('authors/')) {
63
+ imagePaths.add(imagePath);
64
+ }
65
+ });
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ scanDirectory(contentDir);
72
+
73
+ // Также добавляем изображения из public/img/examples/ кроме авторов
74
+ const examplesDir = path.join(rootDir, 'public/img/examples');
75
+ if (fs.existsSync(examplesDir)) {
76
+ function addExampleImages(dir, relativePath = '') {
77
+ // Пропускаем папку authors - аватары не нужно обрабатывать
78
+ if (relativePath.includes('authors/')) return;
79
+
80
+ const items = fs.readdirSync(dir);
81
+ for (const item of items) {
82
+ const itemPath = path.join(dir, item);
83
+ const stat = fs.statSync(itemPath);
84
+
85
+ if (stat.isDirectory()) {
86
+ // Пропускаем папку authors
87
+ if (item === 'authors') continue;
88
+ addExampleImages(itemPath, `${relativePath}${item}/`);
89
+ } else if (item.match(/\.(webp|jpg|jpeg|png)$/i) && !dir.includes('previews')) {
90
+ imagePaths.add(`/img/examples/${relativePath}${item}`);
91
+ }
92
+ }
93
+ }
94
+ addExampleImages(examplesDir);
95
+ }
96
+
97
+ return Array.from(imagePaths);
98
+ }
99
+
100
+ // Функция для очистки всех существующих превьюшек
101
+ function cleanupExistingPreviews() {
102
+ const publicDir = path.join(rootDir, 'public');
103
+
104
+ function removePreviewDirs(dir) {
105
+ if (!fs.existsSync(dir)) return;
106
+
107
+ const items = fs.readdirSync(dir);
108
+
109
+ for (const item of items) {
110
+ const itemPath = path.join(dir, item);
111
+ const stat = fs.statSync(itemPath);
112
+
113
+ if (stat.isDirectory()) {
114
+ if (item === 'previews') {
115
+ console.log(`Removing existing preview directory: ${itemPath}`);
116
+ fs.rmSync(itemPath, { recursive: true, force: true });
117
+ } else {
118
+ removePreviewDirs(itemPath);
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ removePreviewDirs(publicDir);
125
+ }
126
+
127
+ // Функция для создания превьюшки
128
+ async function createPreview(imagePath) {
129
+ const fullImagePath = path.join(rootDir, 'public', imagePath.replace(/^\//, ''));
130
+
131
+ if (!fs.existsSync(fullImagePath)) {
132
+ console.warn(`Image not found: ${fullImagePath}`);
133
+ return;
134
+ }
135
+
136
+ const dir = path.dirname(fullImagePath);
137
+ const ext = path.extname(fullImagePath);
138
+ const name = path.basename(fullImagePath, ext);
139
+ const previewPath = path.join(dir, 'previews', `${name}${ext}`);
140
+
141
+ // Создаем папку previews если её нет
142
+ const previewDir = path.dirname(previewPath);
143
+ if (!fs.existsSync(previewDir)) {
144
+ fs.mkdirSync(previewDir, { recursive: true });
145
+ }
146
+
147
+ try {
148
+ await sharp(fullImagePath)
149
+ .resize(previewWidth, previewHeight, { fit: 'cover' })
150
+ .toFile(previewPath);
151
+
152
+ console.log(`Preview created: ${previewPath}`);
153
+ } catch (error) {
154
+ console.error(`Error processing ${fullImagePath}:`, error.message);
155
+ }
156
+ }
157
+
158
+ // Основная функция
159
+ async function generatePreviews() {
160
+ console.log('Cleaning up existing previews...');
161
+ cleanupExistingPreviews();
162
+
163
+ console.log('Scanning content for images...');
164
+ const imagePaths = extractImagePaths();
165
+
166
+ console.log(`Found ${imagePaths.length} images to process`);
167
+
168
+ for (const imagePath of imagePaths) {
169
+ await createPreview(imagePath);
170
+ }
171
+
172
+ console.log('Preview generation completed!');
173
+ }
174
+
175
+ generatePreviews().catch(console.error);
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import { fileURLToPath, pathToFileURL } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ // Определяем корневые папки
11
+ const isInNodeModules = __dirname.includes('node_modules');
12
+ const isSourceProject = !isInNodeModules && (__dirname.includes('core-maugli-blog') || process.cwd().includes('core-maugli-blog'));
13
+
14
+ const packageRoot = isInNodeModules
15
+ ? path.join(__dirname, '../../..', 'node_modules', 'core-maugli') // из node_modules
16
+ : path.join(__dirname, '..'); // из исходников
17
+
18
+ const userRoot = isInNodeModules
19
+ ? path.join(__dirname, '../../..') // корень пользовательского проекта
20
+ : process.env.INIT_CWD || process.cwd(); // для разработки
21
+
22
+ // Список папок и файлов для полного обновления (перезаписи)
23
+ const FORCE_UPDATE_PATHS = [
24
+ 'src/components',
25
+ 'src/layouts',
26
+ 'src/pages',
27
+ 'src/utils',
28
+ 'src/scripts',
29
+ 'src/icons',
30
+ 'src/i18n',
31
+ 'public/flags',
32
+ 'public/img/default'
33
+ // Исключили src/styles - может содержать пользовательские стили
34
+ ];
35
+
36
+ // Список файлов, которые НЕ должны перезаписываться (пользовательские)
37
+ const PRESERVE_PATHS = [
38
+ 'src/content',
39
+ 'src/config/maugli.config.ts', // обновляется через upgrade-config.js
40
+ 'src/styles/global.css', // может быть кастомизирован пользователем
41
+ 'package.json',
42
+ 'astro.config.mjs',
43
+ 'tailwind.config.js',
44
+ 'tsconfig.json'
45
+ ];
46
+
47
+ async function copyDirectory(src, dest) {
48
+ try {
49
+ await fs.mkdir(dest, { recursive: true });
50
+
51
+ const entries = await fs.readdir(src, { withFileTypes: true });
52
+
53
+ for (const entry of entries) {
54
+ const srcPath = path.join(src, entry.name);
55
+ const destPath = path.join(dest, entry.name);
56
+
57
+ if (entry.isDirectory()) {
58
+ await copyDirectory(srcPath, destPath);
59
+ } else {
60
+ await fs.copyFile(srcPath, destPath);
61
+ console.log(`Updated: ${path.relative(userRoot, destPath)}`);
62
+ }
63
+ }
64
+ } catch (error) {
65
+ console.warn(`Warning: Could not copy ${src} to ${dest}:`, error.message);
66
+ }
67
+ }
68
+
69
+ async function updateStyles() {
70
+ const srcStylesPath = path.join(packageRoot, 'src/styles');
71
+ const destStylesPath = path.join(userRoot, 'src/styles');
72
+
73
+ try {
74
+ // Проверяем, существует ли папка styles в пакете
75
+ await fs.stat(srcStylesPath);
76
+
77
+ // Проверяем, есть ли уже пользовательские стили
78
+ try {
79
+ const userGlobalCss = path.join(destStylesPath, 'global.css');
80
+ await fs.stat(userGlobalCss);
81
+ console.log('📝 Preserving user styles (global.css exists)');
82
+
83
+ // Копируем только новые файлы стилей, не трогая global.css
84
+ const entries = await fs.readdir(srcStylesPath, { withFileTypes: true });
85
+ await fs.mkdir(destStylesPath, { recursive: true });
86
+
87
+ for (const entry of entries) {
88
+ if (entry.name !== 'global.css') {
89
+ const srcFile = path.join(srcStylesPath, entry.name);
90
+ const destFile = path.join(destStylesPath, entry.name);
91
+ await fs.copyFile(srcFile, destFile);
92
+ console.log(`Updated style: ${entry.name}`);
93
+ }
94
+ }
95
+ } catch {
96
+ // Пользовательских стилей нет, копируем все
97
+ await copyDirectory(srcStylesPath, destStylesPath);
98
+ console.log('📝 Copied default styles');
99
+ }
100
+ } catch (error) {
101
+ console.warn('Warning: Could not update styles:', error.message);
102
+ }
103
+ }
104
+
105
+ async function updateComponents() {
106
+ console.log('🔄 Updating Maugli components and assets...');
107
+
108
+ // Проверяем, что мы не в исходном проекте (чтобы не удалить исходники)
109
+ if (isSourceProject) {
110
+ console.log('⚠️ Skipping component update (running in source project)');
111
+ return;
112
+ }
113
+
114
+ // Дополнительная проверка
115
+ if (packageRoot === userRoot) {
116
+ console.log('⚠️ Skipping component update (packageRoot equals userRoot)');
117
+ return;
118
+ }
119
+
120
+ let updatedCount = 0;
121
+
122
+ for (const updatePath of FORCE_UPDATE_PATHS) {
123
+ const srcPath = path.join(packageRoot, updatePath);
124
+ const destPath = path.join(userRoot, updatePath);
125
+
126
+ try {
127
+ // Проверяем, существует ли исходная папка/файл
128
+ const stats = await fs.stat(srcPath);
129
+
130
+ if (stats.isDirectory()) {
131
+ // Удаляем существующую папку и копируем новую
132
+ try {
133
+ await fs.rm(destPath, { recursive: true, force: true });
134
+ } catch (e) {
135
+ // Папки может не быть - это нормально
136
+ }
137
+
138
+ await copyDirectory(srcPath, destPath);
139
+ updatedCount++;
140
+ } else if (stats.isFile()) {
141
+ // Копируем отдельный файл
142
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
143
+ await fs.copyFile(srcPath, destPath);
144
+ console.log(`Updated: ${path.relative(userRoot, destPath)}`);
145
+ updatedCount++;
146
+ }
147
+ } catch (error) {
148
+ if (error.code !== 'ENOENT') {
149
+ console.warn(`Warning: Could not update ${updatePath}:`, error.message);
150
+ }
151
+ }
152
+ }
153
+
154
+ // Обрабатываем стили отдельно
155
+ await updateStyles();
156
+
157
+ console.log(`✅ Updated ${updatedCount} component directories/files`);
158
+ }
159
+
160
+ async function main() {
161
+ try {
162
+ await updateComponents();
163
+ console.log('🎉 Component update completed successfully!');
164
+ } catch (error) {
165
+ console.error('❌ Component update failed:', error);
166
+ process.exit(1);
167
+ }
168
+ }
169
+
170
+ // Запускаем только если вызывается напрямую
171
+ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
172
+ main();
173
+ }
174
+
175
+ export { updateComponents };
@@ -1,14 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'fs/promises';
4
- import path from 'path';
5
4
  import os from 'os';
6
- import { fileURLToPath, pathToFileURL } from 'url';
5
+ import path from 'path';
7
6
  import ts from 'typescript';
7
+ import { fileURLToPath, pathToFileURL } from 'url';
8
8
 
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = path.dirname(__filename);
11
11
 
12
+ // Импортируем функцию обновления компонентов
13
+ async function importUpdateComponents() {
14
+ try {
15
+ const { updateComponents } = await import('./update-components.js');
16
+ return updateComponents;
17
+ } catch (error) {
18
+ console.warn('Could not load update-components.js:', error.message);
19
+ return null;
20
+ }
21
+ }
22
+
12
23
  const defaultConfigPath = path.join(__dirname, '../src/config/maugli.config.ts');
13
24
  const userRoot = process.env.INIT_CWD || process.cwd();
14
25
  const userConfigPath = path.join(userRoot, 'src/config/maugli.config.ts');
@@ -43,6 +54,15 @@ function mergeMissing(target, source) {
43
54
  }
44
55
 
45
56
  async function main() {
57
+ console.log('🔄 Starting Maugli upgrade process...');
58
+
59
+ // Сначала обновляем компоненты
60
+ const updateComponents = await importUpdateComponents();
61
+ if (updateComponents) {
62
+ await updateComponents();
63
+ }
64
+
65
+ // Затем обновляем конфиг
46
66
  const pkg = await loadTsModule(defaultConfigPath);
47
67
  const defCfg = pkg.maugliConfig;
48
68
  const newVersion = pkg.MAUGLI_CONFIG_VERSION || defCfg.configVersion;
@@ -50,7 +70,7 @@ async function main() {
50
70
  try {
51
71
  await fs.access(userConfigPath);
52
72
  } catch {
53
- console.warn(`User config not found at ${userConfigPath}, skipping upgrade`);
73
+ console.warn(`User config not found at ${userConfigPath}, skipping config upgrade`);
54
74
  return;
55
75
  }
56
76
 
@@ -24,15 +24,7 @@ const current = availableLanguages.find((l) => l.code === currentLang) || availa
24
24
  class="flag-box flex items-center justify-center w-7 h-7 rounded-[var(--radius-main)] shadow-[var(--shadow-main)]"
25
25
  style="min-width:28px; min-height:28px; max-width:28px; max-height:28px;"
26
26
  >
27
- {current && (
28
- <img
29
- src={current.icon.replace(/^\/public\/|^\/|^flags\//, '/src/icons/flags/')}
30
- alt={current.code.toUpperCase()}
31
- width="28"
32
- height="28"
33
- style="pointer-events:none;"
34
- />
35
- )}
27
+ {current && <img src={current.icon} alt={current.code.toUpperCase()} width="28" height="28" style="pointer-events:none;" />}
36
28
  </span>
37
29
  <svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
38
30
  <path
@@ -65,13 +57,7 @@ const current = availableLanguages.find((l) => l.code === currentLang) || availa
65
57
  class="flag-box flex items-center justify-center w-7 h-7 rounded-[var(--radius-main)] shadow-[var(--shadow-main)]"
66
58
  style="min-width:28px; min-height:28px; max-width:28px; max-height:28px;"
67
59
  >
68
- <img
69
- src={lang.icon.replace(/^\/public\/|^\/|^flags\//, '/src/icons/flags/')}
70
- alt={lang.code.toUpperCase()}
71
- width="28"
72
- height="28"
73
- style="pointer-events:none;"
74
- />
60
+ <img src={lang.icon} alt={lang.code.toUpperCase()} width="28" height="28" style="pointer-events:none;" />
75
61
  </span>
76
62
  <span class="text-heading">{lang.label}</span>
77
63
  </a>
package/astro.config.mjs DELETED
@@ -1,92 +0,0 @@
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 { imagetools } from 'vite-imagetools';
6
- import { VitePWA } from 'vite-plugin-pwa';
7
- import siteConfig from './src/data/site-config';
8
- import remarkSlug from 'remark-slug';
9
- import customSlugify from './src/utils/remark-slugify';
10
- import { maugliConfig } from './src/config/maugli.config';
11
-
12
- export const pwaOptions = {
13
- registerType: 'autoUpdate',
14
- includeAssets: ['favicon.svg', 'favicon.ico', 'robots.txt', 'apple-touch-icon.png'],
15
- manifest: {
16
- name: "Maugli Blog",
17
- short_name: "Maugli",
18
- start_url: "/",
19
- display: "standalone",
20
- background_color: maugliConfig.pwa?.backgroundColor ?? '#ffffff',
21
- theme_color: maugliConfig.pwa?.themeColor ?? '#0cbf11',
22
- icons: maugliConfig.pwa?.icons ?? [
23
- {
24
- src: "/icon-192.png",
25
- sizes: "192x192",
26
- type: "image/png",
27
- purpose: "any maskable",
28
- },
29
- {
30
- src: "/icon-512.png",
31
- sizes: "512x512",
32
- type: "image/png",
33
- },
34
- ],
35
- },
36
- workbox: {
37
- navigateFallback: '/index.html',
38
- cleanupOutdatedCaches: true,
39
- navigateFallbackDenylist: [/^\/api\//],
40
- clientsClaim: true,
41
- globPatterns: ['**/*.{js,css,html,png,jpg,jpeg,webp,svg}'],
42
- runtimeCaching: [
43
- {
44
- urlPattern: ({ request }) => request.destination === 'image',
45
- handler: 'CacheFirst',
46
- options: {
47
- cacheName: 'images-cache',
48
- expiration: {
49
- maxEntries: 50,
50
- maxAgeSeconds: 30 * 24 * 60 * 60 // 30 дней
51
- }
52
- }
53
- },
54
- {
55
- urlPattern: ({ request }) => request.destination === 'font',
56
- handler: 'CacheFirst',
57
- options: {
58
- cacheName: 'fonts-cache',
59
- expiration: {
60
- maxEntries: 20,
61
- maxAgeSeconds: 365 * 24 * 60 * 60 // 1 год
62
- }
63
- }
64
- }
65
- ]
66
- },
67
- devOptions: {
68
- enabled: true, // чтобы работал в деве
69
- type: 'module',
70
- }
71
- };
72
-
73
- // https://astro.build/config
74
- export default defineConfig({
75
- site: siteConfig.website,
76
- integrations: [
77
- mdx(),
78
- sitemap()
79
- ],
80
- vite: {
81
- plugins: [
82
- tailwindcss(),
83
- imagetools(),
84
- VitePWA(pwaOptions)
85
- ]
86
- },
87
- markdown: {
88
- remarkPlugins: [
89
- [remarkSlug, { slug: customSlugify }]
90
- ]
91
- }
92
- });
package/bin/init.js DELETED
@@ -1,201 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { execSync } from 'child_process';
4
- import { cpSync, existsSync, readFileSync, writeFileSync } from 'fs';
5
- import path from 'path';
6
- import readline from 'readline';
7
- import { fileURLToPath } from 'url';
8
-
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
- const templateRoot = path.join(__dirname, '..');
12
-
13
- function getLanguageCodes() {
14
- const file = readFileSync(path.join(templateRoot, 'src/i18n/languages.ts'), 'utf8');
15
- const codes = [];
16
- const regex = /{\s*code:\s*'([^']+)'/g;
17
- let match;
18
- while ((match = regex.exec(file)) !== null) {
19
- codes.push(match[1]);
20
- }
21
- return codes;
22
- }
23
-
24
- function promptLang(codes) {
25
- return new Promise(resolve => {
26
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
27
- rl.question(`Choose language (${codes.join(', ')}): `, answer => {
28
- rl.close();
29
- resolve(codes.includes(answer) ? answer : codes[0]);
30
- });
31
- });
32
- }
33
-
34
- function promptRepo() {
35
- return new Promise(resolve => {
36
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
37
- rl.question('Repository URL: ', answer => {
38
- rl.close();
39
- resolve(answer.trim());
40
- });
41
- });
42
- }
43
-
44
- async function getRepoUrl(targetDir, repoOption) {
45
- if (repoOption) return repoOption;
46
- try {
47
- const url = execSync('git remote get-url origin', {
48
- cwd: targetDir,
49
- stdio: ['ignore', 'pipe', 'ignore']
50
- })
51
- .toString()
52
- .trim();
53
- if (url) return url;
54
- } catch {
55
- // ignore
56
- }
57
- return await promptRepo();
58
- }
59
-
60
- function updateReadme(targetDir, repoUrl) {
61
- if (!repoUrl) return;
62
- const readmePath = path.join(targetDir, 'README.md');
63
- if (!existsSync(readmePath)) return;
64
- let content = readFileSync(readmePath, 'utf8');
65
- const pattern = /https:\/\/app\.netlify\.com\/start\/deploy\?repository=[^\)\s]+/;
66
- content = content.replace(
67
- pattern,
68
- `https://app.netlify.com/start/deploy?repository=${repoUrl}`
69
- );
70
- writeFileSync(readmePath, content);
71
- console.log('Updated Netlify link in README.md');
72
- }
73
-
74
- function updateConfig(targetDir, lang, repoUrl) {
75
- const configPath = path.join(targetDir, 'src', 'config', 'maugli.config.ts');
76
- if (!existsSync(configPath)) return;
77
- let content = readFileSync(configPath, 'utf8');
78
- content = content.replace(/defaultLang:\s*'[^']*'/, `defaultLang: '${lang}'`);
79
- const multiMatch = content.match(/enableMultiLang:\s*(true|false)/);
80
- const multi = multiMatch ? multiMatch[1] === 'true' : false;
81
- content = content.replace(/showLangSwitcher:\s*(true|false)/, `showLangSwitcher: ${multi}`);
82
-
83
- // Update repository URL if provided
84
- if (repoUrl) {
85
- content = content.replace(
86
- /repository:\s*{[^}]*url:\s*'[^']*'/,
87
- `repository: {\n url: '${repoUrl}'`
88
- );
89
- }
90
-
91
- writeFileSync(configPath, content);
92
- console.log(`Configured default language to ${lang}`);
93
- if (repoUrl) {
94
- console.log(`Configured repository URL to ${repoUrl}`);
95
- }
96
- }
97
-
98
- export default async function init(targetName, langOption, repoOption) {
99
- const targetDir = targetName ? path.resolve(targetName) : process.cwd();
100
- const codes = getLanguageCodes();
101
- const lang = langOption && codes.includes(langOption) ? langOption : await promptLang(codes);
102
-
103
- function copyItem(item) {
104
- const src = path.join(templateRoot, item);
105
- const dest = path.join(targetDir, item);
106
-
107
- if (!existsSync(src)) {
108
- console.log(`Skipped ${item} (not found)`);
109
- return;
110
- }
111
-
112
- cpSync(src, dest, { recursive: true });
113
- console.log(`Copied ${item}`);
114
- }
115
-
116
- // Copy package files first so npm install works correctly
117
- ['package.json', 'package-lock.json'].forEach(file => {
118
- if (existsSync(path.join(templateRoot, file))) {
119
- copyItem(file);
120
- }
121
- });
122
-
123
- const items = [
124
- 'astro.config.mjs',
125
- 'tsconfig.json',
126
- 'vite.config.js',
127
- 'public',
128
- 'src',
129
- 'scripts',
130
- 'typograf-batch.js',
131
- 'resize-all.cjs',
132
- 'README.md',
133
- 'LICENSE'
134
- ];
135
- items.forEach(copyItem);
136
-
137
- const repoUrl = await getRepoUrl(targetDir, repoOption);
138
- updateReadme(targetDir, repoUrl);
139
-
140
- // Create essential config files
141
- const gitignoreContent = `
142
- # Dependencies
143
- node_modules/
144
- .pnpm-debug.log*
145
-
146
- # Environment
147
- .env
148
- .env.local
149
- .env.production
150
-
151
- # Build outputs
152
- dist/
153
- .astro/
154
-
155
- # Generated files
156
- .DS_Store
157
- .vscode/settings.json
158
-
159
- # Cache
160
- .typograf-cache.json
161
- `;
162
-
163
- const prettierrcContent = `{
164
- "semi": true,
165
- "singleQuote": true,
166
- "tabWidth": 2,
167
- "trailingComma": "es5",
168
- "printWidth": 100,
169
- "plugins": ["prettier-plugin-tailwindcss"]
170
- }
171
- `;
172
-
173
- writeFileSync(path.join(targetDir, '.gitignore'), gitignoreContent.trim());
174
- console.log('Created .gitignore');
175
-
176
- writeFileSync(path.join(targetDir, '.prettierrc'), prettierrcContent);
177
- console.log('Created .prettierrc');
178
-
179
- execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
180
- updateConfig(targetDir, lang, repoUrl);
181
- }
182
-
183
- // Если скрипт запускается напрямую
184
- if (import.meta.url === `file://${process.argv[1]}`) {
185
- const args = process.argv.slice(2);
186
- let targetName;
187
- let lang;
188
- let repo;
189
- for (let i = 0; i < args.length; i++) {
190
- if (args[i] === '--lang' && i + 1 < args.length) {
191
- lang = args[i + 1];
192
- i++;
193
- } else if (args[i] === '--repo' && i + 1 < args.length) {
194
- repo = args[i + 1];
195
- i++;
196
- } else {
197
- targetName = args[i];
198
- }
199
- }
200
- await init(targetName, lang, repo);
201
- }
package/resize-all.cjs DELETED
@@ -1,29 +0,0 @@
1
- // resize-all.js
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 outputDir = './public';
11
-
12
- // Перебираем все файлы в исходной папке
13
- fs.readdirSync(inputDir).forEach(file => {
14
- const ext = path.extname(file);
15
- const base = path.basename(file, ext);
16
- if (!['.jpg', '.jpeg', '.png', '.webp'].includes(ext.toLowerCase())) return;
17
-
18
- sizes.forEach(width => {
19
- sharp(path.join(inputDir, file))
20
- .resize(width)
21
- .toFile(path.join(outputDir, `${base}-${width}${ext}`), (err) => {
22
- if (err) console.error(`Ошибка на ${base}-${width}${ext}:`, err);
23
- else console.log(`Сделан: ${base}-${width}${ext}`);
24
- });
25
- });
26
-
27
- // Копируем оригинал тоже (максимальный)
28
- fs.copyFileSync(path.join(inputDir, file), path.join(outputDir, `${base}${ext}`));
29
- });
package/tsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "astro/tsconfigs/strict",
3
- "include": [".astro/types.d.ts", "**/*"],
4
- "exclude": ["dist"],
5
- "compilerOptions": {
6
- "strictNullChecks": true
7
- }
8
- }
package/typograf-batch.js DELETED
@@ -1,49 +0,0 @@
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 DELETED
@@ -1,11 +0,0 @@
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
- });