@windrun-huaiin/dev-scripts 6.8.1 → 6.9.0
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/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +137 -1410
- package/dist/cli.mjs +145 -432
- package/dist/commands/check-translations.d.ts +3 -0
- package/dist/commands/check-translations.d.ts.map +1 -0
- package/dist/commands/check-translations.js +132 -0
- package/dist/commands/check-translations.mjs +130 -0
- package/dist/commands/clean-translations.d.ts +3 -0
- package/dist/commands/clean-translations.d.ts.map +1 -0
- package/dist/commands/clean-translations.js +148 -0
- package/dist/commands/clean-translations.mjs +146 -0
- package/dist/commands/create-diaomao-app.d.ts +2 -0
- package/dist/commands/create-diaomao-app.d.ts.map +1 -0
- package/dist/commands/create-diaomao-app.js +151 -0
- package/dist/commands/create-diaomao-app.mjs +149 -0
- package/dist/commands/deep-clean.d.ts +3 -0
- package/dist/commands/deep-clean.d.ts.map +1 -0
- package/dist/commands/deep-clean.js +119 -0
- package/dist/commands/deep-clean.mjs +117 -0
- package/dist/commands/easy-changeset.d.ts +2 -0
- package/dist/commands/easy-changeset.d.ts.map +1 -0
- package/dist/commands/easy-changeset.js +39 -0
- package/dist/commands/easy-changeset.mjs +37 -0
- package/dist/commands/generate-blog-index.d.ts +3 -0
- package/dist/commands/generate-blog-index.d.ts.map +1 -0
- package/dist/commands/generate-blog-index.js +302 -0
- package/dist/commands/generate-blog-index.mjs +300 -0
- package/dist/commands/generate-nextjs-architecture.d.ts +3 -0
- package/dist/commands/generate-nextjs-architecture.d.ts.map +1 -0
- package/dist/commands/generate-nextjs-architecture.js +84 -0
- package/dist/commands/generate-nextjs-architecture.mjs +82 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +173 -0
- package/dist/config/index.mjs +170 -0
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +80 -0
- package/dist/config/schema.mjs +78 -0
- package/dist/index.d.ts +6 -49
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -996
- package/dist/index.mjs +4 -3
- package/dist/utils/file-scanner.d.ts +22 -0
- package/dist/utils/file-scanner.d.ts.map +1 -0
- package/dist/utils/file-scanner.js +70 -0
- package/dist/utils/file-scanner.mjs +65 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +63 -0
- package/dist/utils/logger.mjs +61 -0
- package/dist/utils/translation-parser.d.ts +29 -0
- package/dist/utils/translation-parser.d.ts.map +1 -0
- package/dist/utils/translation-parser.js +225 -0
- package/dist/utils/translation-parser.mjs +218 -0
- package/package.json +5 -5
- package/dist/chunk-GVR6HFHM.mjs +0 -989
- package/dist/chunk-GVR6HFHM.mjs.map +0 -1
- package/dist/cli.d.mts +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cli.mjs.map +0 -1
- package/dist/index.d.mts +0 -49
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var logger = require('../utils/logger.js');
|
|
6
|
+
var fileScanner = require('../utils/file-scanner.js');
|
|
7
|
+
|
|
8
|
+
function parseFrontmatter(fileContent) {
|
|
9
|
+
const frontmatter = {};
|
|
10
|
+
const match = fileContent.match(/^---([\s\S]*?)---/);
|
|
11
|
+
if (match && match[1]) {
|
|
12
|
+
const lines = match[1].trim().split('\n');
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
const [key, ...valueParts] = line.split(':');
|
|
15
|
+
if (key && valueParts.length > 0) {
|
|
16
|
+
const value = valueParts.join(':').trim();
|
|
17
|
+
if (key.trim() === 'title')
|
|
18
|
+
frontmatter.title = value;
|
|
19
|
+
if (key.trim() === 'description')
|
|
20
|
+
frontmatter.description = value;
|
|
21
|
+
if (key.trim() === 'icon')
|
|
22
|
+
frontmatter.icon = value;
|
|
23
|
+
if (key.trim() === 'date')
|
|
24
|
+
frontmatter.date = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return frontmatter;
|
|
29
|
+
}
|
|
30
|
+
function getIconComponentString(iconName) {
|
|
31
|
+
if (!iconName)
|
|
32
|
+
return undefined;
|
|
33
|
+
return `<${iconName} />`;
|
|
34
|
+
}
|
|
35
|
+
function getCurrentDateString() {
|
|
36
|
+
const now = new Date();
|
|
37
|
+
const year = now.getFullYear();
|
|
38
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
39
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
40
|
+
return `${year}-${month}-${day}`;
|
|
41
|
+
}
|
|
42
|
+
function updateFrontmatterDate(frontmatter) {
|
|
43
|
+
const currentDate = getCurrentDateString();
|
|
44
|
+
// Check if date field exists
|
|
45
|
+
if (frontmatter.includes('date:')) {
|
|
46
|
+
// Replace existing date
|
|
47
|
+
return frontmatter.replace(/date:\s*[^\n]*/, `date: ${currentDate}`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Add date field before the closing ---
|
|
51
|
+
return frontmatter.replace(/---$/, `date: ${currentDate}\n---`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function getBlogPrefix(config) {
|
|
55
|
+
if (config.blog?.prefix === undefined || config.blog?.prefix === null) {
|
|
56
|
+
return 'blog';
|
|
57
|
+
}
|
|
58
|
+
else if (typeof config.blog?.prefix === 'string' && config.blog?.prefix.trim() === '') {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
return String(config.blog.prefix).trim();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function getAllBlogArticles(blogDir, cwd, logger) {
|
|
66
|
+
const articles = [];
|
|
67
|
+
const blogPath = path.join(cwd, blogDir);
|
|
68
|
+
try {
|
|
69
|
+
const files = fs.readdirSync(blogPath);
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
if (file.endsWith('.mdx') && file !== 'index.mdx') {
|
|
72
|
+
const slug = file.replace(/\.mdx$/, '');
|
|
73
|
+
const filePath = path.join(blogPath, file);
|
|
74
|
+
try {
|
|
75
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
76
|
+
const fm = parseFrontmatter(content);
|
|
77
|
+
if (!fm.title) {
|
|
78
|
+
logger.warn(`Article "${file}" is missing a title in its frontmatter. Skipping.`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
articles.push({
|
|
82
|
+
slug,
|
|
83
|
+
title: fm.title,
|
|
84
|
+
description: fm.description,
|
|
85
|
+
frontmatterIcon: fm.icon,
|
|
86
|
+
date: fm.date,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (readError) {
|
|
90
|
+
logger.warn(`Could not read or parse frontmatter for "${file}": ${readError}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (dirError) {
|
|
96
|
+
logger.error(`Could not read blog directory: ${dirError}`);
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return articles;
|
|
100
|
+
}
|
|
101
|
+
async function generateBlogIndex(config, cwd = typeof process !== 'undefined' ? process.cwd() : '.') {
|
|
102
|
+
const logger$1 = new logger.Logger(config);
|
|
103
|
+
logger$1.warn('==============================');
|
|
104
|
+
logger$1.warn(`‼️ Current working directory: ⭕ ${cwd} ⭕`);
|
|
105
|
+
logger$1.warn('==============================');
|
|
106
|
+
try {
|
|
107
|
+
if (!config.blog) {
|
|
108
|
+
logger$1.error('Blog configuration is missing. Please configure blog settings.');
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
logger$1.log('Starting to generate blog index...');
|
|
112
|
+
const blogPath = path.join(cwd, config.blog.mdxDir);
|
|
113
|
+
const indexFile = path.join(blogPath, config.blog.outputFile || 'index.mdx');
|
|
114
|
+
const metaFile = path.join(blogPath, config.blog.metaFile || 'meta.json');
|
|
115
|
+
const iocFile = path.join(blogPath, `${config.blog.iocSlug || 'ioc'}.mdx`);
|
|
116
|
+
const iocSlug = config.blog.iocSlug || 'ioc';
|
|
117
|
+
const blogPrefix = getBlogPrefix(config);
|
|
118
|
+
let meta = { pages: [] };
|
|
119
|
+
const metaContent = fileScanner.readJsonFile(metaFile);
|
|
120
|
+
if (metaContent) {
|
|
121
|
+
meta = metaContent;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
logger$1.warn(`Could not read or parse ${metaFile}. No articles will be marked as featured.`);
|
|
125
|
+
}
|
|
126
|
+
// 统一提取被隐藏的 slug
|
|
127
|
+
const hiddenSlugs = new Set(meta.pages.filter(p => p.startsWith('!')).map(p => p.slice(1)));
|
|
128
|
+
// ioc 相关处理
|
|
129
|
+
const featuredSlugs = meta.pages
|
|
130
|
+
.filter(p => !p.startsWith('!'))
|
|
131
|
+
.map(p => p.endsWith('.mdx') ? p.slice(0, -4) : p)
|
|
132
|
+
.filter(slug => slug !== 'index' && slug !== '...');
|
|
133
|
+
logger$1.log(`Featured slugs (meta-config): ${featuredSlugs.join(', ')}`);
|
|
134
|
+
const allArticles = await getAllBlogArticles(config.blog.mdxDir, cwd, logger$1);
|
|
135
|
+
logger$1.log(`Found ${allArticles.length} all articles.`);
|
|
136
|
+
// 过滤所有被隐藏的 slug
|
|
137
|
+
const visibleArticles = allArticles.filter(a => !hiddenSlugs.has(a.slug));
|
|
138
|
+
// ioc article 处理
|
|
139
|
+
const iocArticle = visibleArticles.find(a => a.slug === iocSlug);
|
|
140
|
+
const filteredArticles = visibleArticles.filter(a => a.slug !== iocSlug);
|
|
141
|
+
if (filteredArticles.length === 0 && featuredSlugs.length === 0) {
|
|
142
|
+
logger$1.warn("No articles found or featured. The generated index might be empty or minimal.");
|
|
143
|
+
}
|
|
144
|
+
const featuredArticles = [];
|
|
145
|
+
const pastArticles = [];
|
|
146
|
+
filteredArticles.forEach(article => {
|
|
147
|
+
if (featuredSlugs.includes(article.slug)) {
|
|
148
|
+
featuredArticles.push(article);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
pastArticles.push(article);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
// Sort articles by date in descending order (newest first)
|
|
155
|
+
const sortByDateDesc = (a, b) => {
|
|
156
|
+
if (a.date && b.date) {
|
|
157
|
+
return b.date.localeCompare(a.date); // Newest first
|
|
158
|
+
}
|
|
159
|
+
if (a.date)
|
|
160
|
+
return -1; // Articles with date come before those without
|
|
161
|
+
if (b.date)
|
|
162
|
+
return 1; // Articles with date come before those without
|
|
163
|
+
return 0; // Keep original order if both lack dates
|
|
164
|
+
};
|
|
165
|
+
featuredArticles.sort(sortByDateDesc);
|
|
166
|
+
pastArticles.sort(sortByDateDesc);
|
|
167
|
+
logger$1.log(`Found ${featuredArticles.length} featured articles (sorted by date).`);
|
|
168
|
+
logger$1.log(`Found ${pastArticles.length} past articles (sorted by date).`);
|
|
169
|
+
// Preserve existing frontmatter or use a default
|
|
170
|
+
let currentFileFrontmatter = '---\ntitle: Blog\ndescription: Articles and thoughts about various topics.\nicon: Rss\n---';
|
|
171
|
+
try {
|
|
172
|
+
const currentIndexContent = fs.readFileSync(indexFile, 'utf-8');
|
|
173
|
+
const frontmatterMatch = currentIndexContent.match(/^---([\s\S]*?)---/);
|
|
174
|
+
if (frontmatterMatch && frontmatterMatch[0]) {
|
|
175
|
+
currentFileFrontmatter = frontmatterMatch[0];
|
|
176
|
+
logger$1.log('Preserving existing frontmatter from index.mdx');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
logger$1.warn('Could not read existing index.mdx or parse its frontmatter. Using default frontmatter.');
|
|
181
|
+
}
|
|
182
|
+
// Update date field in frontmatter
|
|
183
|
+
currentFileFrontmatter = updateFrontmatterDate(currentFileFrontmatter);
|
|
184
|
+
let mdxContent = `${currentFileFrontmatter}\n\n`;
|
|
185
|
+
const createCard = (article) => {
|
|
186
|
+
const iconString = getIconComponentString(article.frontmatterIcon);
|
|
187
|
+
const iconProp = iconString ? `icon={${iconString}}` : '';
|
|
188
|
+
// Escape only double quotes in title for JSX attribute
|
|
189
|
+
const escapedTitle = (article.title || '').replace(/"/g, '"');
|
|
190
|
+
// Content of the card - should be raw, as it might be MDX
|
|
191
|
+
const cardContent = article.date || article.description || '';
|
|
192
|
+
// Ensure there's a space before href if iconProp is present and not empty
|
|
193
|
+
const finalIconProp = iconProp ? `${iconProp} ` : '';
|
|
194
|
+
// refer path is /locale/blog, this is blog root dir, so here is blog/X, then you'll get /locale/blog/X
|
|
195
|
+
const href = blogPrefix ? `${blogPrefix}/${article.slug}` : `${article.slug}`;
|
|
196
|
+
return ` <ZiaCard ${finalIconProp} href="${href}" title="${escapedTitle}">\n ${cardContent}\n </ZiaCard>\n`;
|
|
197
|
+
};
|
|
198
|
+
if (featuredArticles.length > 0) {
|
|
199
|
+
mdxContent += `## Feature List\n\n<Cards>\n`;
|
|
200
|
+
featuredArticles.forEach(article => { mdxContent += createCard(article); });
|
|
201
|
+
mdxContent += `</Cards>\n\n`;
|
|
202
|
+
}
|
|
203
|
+
if (pastArticles.length > 0) {
|
|
204
|
+
mdxContent += `## Past List\n\n<Cards>\n`;
|
|
205
|
+
pastArticles.forEach(article => { mdxContent += createCard(article); });
|
|
206
|
+
mdxContent += `</Cards>\n`;
|
|
207
|
+
}
|
|
208
|
+
// add Monthly Summary block separately
|
|
209
|
+
if (iocArticle) {
|
|
210
|
+
mdxContent += `\n## Monthly Summary\n\n<Cards>\n`;
|
|
211
|
+
const iocHref = blogPrefix ? `${blogPrefix}/${iocSlug}` : `${iocSlug}`;
|
|
212
|
+
mdxContent += ` <ZiaCard href="${iocHref}" title="Overview">\n ${getCurrentDateString()}\n </ZiaCard>\n`;
|
|
213
|
+
mdxContent += `</Cards>\n`;
|
|
214
|
+
}
|
|
215
|
+
if (featuredArticles.length === 0 && pastArticles.length === 0 && !iocArticle) {
|
|
216
|
+
mdxContent += "## Ooops\nNo blog posts found yet. Stay tuned!\n";
|
|
217
|
+
}
|
|
218
|
+
fs.writeFileSync(indexFile, mdxContent);
|
|
219
|
+
logger$1.success(`Successfully generated ${indexFile}`);
|
|
220
|
+
// generate monthly statistics
|
|
221
|
+
await generateMonthlyBlogSummary(config, visibleArticles, iocFile, iocSlug, logger$1);
|
|
222
|
+
logger$1.log('Blog index generation completed successfully!');
|
|
223
|
+
logger$1.saveToFile('generate-blog.log', cwd);
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
logger$1.error(`Error generating blog index: ${error}`);
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* generate blog monthly statistics details
|
|
233
|
+
*/
|
|
234
|
+
async function generateMonthlyBlogSummary(config, articles, iocFile, iocSlug, logger) {
|
|
235
|
+
try {
|
|
236
|
+
// filter out articles without date and slug is ioc
|
|
237
|
+
const articlesWithDate = articles.filter(a => a.date && a.slug !== iocSlug);
|
|
238
|
+
// group by month
|
|
239
|
+
const monthMap = {};
|
|
240
|
+
for (const art of articlesWithDate) {
|
|
241
|
+
// only take the first 7 digits yyyy-mm
|
|
242
|
+
const month = art.date.slice(0, 7);
|
|
243
|
+
if (!monthMap[month])
|
|
244
|
+
monthMap[month] = [];
|
|
245
|
+
monthMap[month].push({ date: art.date, title: art.title, slug: art.slug });
|
|
246
|
+
}
|
|
247
|
+
// sort months in descending order
|
|
248
|
+
const sortedMonths = Object.keys(monthMap).sort((a, b) => b.localeCompare(a));
|
|
249
|
+
// sort articles by date in descending order
|
|
250
|
+
for (const month of sortedMonths) {
|
|
251
|
+
monthMap[month].sort((a, b) => b.date.localeCompare(a.date));
|
|
252
|
+
}
|
|
253
|
+
// read ioc.mdx original frontmatter
|
|
254
|
+
let frontmatter = '';
|
|
255
|
+
try {
|
|
256
|
+
const content = fs.readFileSync(iocFile, 'utf-8');
|
|
257
|
+
const match = content.match(/^---([\s\S]*?)---/);
|
|
258
|
+
if (match && match[0])
|
|
259
|
+
frontmatter = match[0];
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// File doesn't exist, use default
|
|
263
|
+
}
|
|
264
|
+
// if there is no frontmatter, use the default
|
|
265
|
+
if (!frontmatter) {
|
|
266
|
+
frontmatter = '---\ntitle: Monthly Summary\ndescription: Index and Summary\n---';
|
|
267
|
+
}
|
|
268
|
+
// update date field in frontmatter
|
|
269
|
+
frontmatter = updateFrontmatterDate(frontmatter);
|
|
270
|
+
// generate content
|
|
271
|
+
let mdx = `${frontmatter}\n\n\n## Overview\n<Files>\n`;
|
|
272
|
+
if (sortedMonths.length === 0) {
|
|
273
|
+
mdx += ' <ZiaFile name="Comming Soon" className="opacity-50" disabled/>\n';
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
for (const month of sortedMonths) {
|
|
277
|
+
// Folder name format YYYY-MM(article count)
|
|
278
|
+
const count = monthMap[month].length;
|
|
279
|
+
const folderTitle = `${month}(${count})`;
|
|
280
|
+
// default open the latest month
|
|
281
|
+
const defaultOpen = month === sortedMonths[0] ? ' defaultOpen' : '';
|
|
282
|
+
mdx += ` <ZiaFolder name="${folderTitle}"${defaultOpen}>\n`;
|
|
283
|
+
for (const art of monthMap[month]) {
|
|
284
|
+
// File name="YYYY-MM-DD(Title)" format
|
|
285
|
+
const day = art.date.slice(0, 10);
|
|
286
|
+
// refer path is /locale/blog/ioc, so here is ./X, then you'll get /locale/blog/X
|
|
287
|
+
const href = art.slug ? `./${art.slug}` : '';
|
|
288
|
+
mdx += ` <ZiaFile name="${day}(${art.title})" href="${href}" />\n`;
|
|
289
|
+
}
|
|
290
|
+
mdx += ` </ZiaFolder>\n`;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
mdx += '</Files>\n\n';
|
|
294
|
+
fs.writeFileSync(iocFile, mdx);
|
|
295
|
+
logger.success(`Successfully generated Monthly Blog Summary: ${iocFile}`);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
logger.error(`Error generating monthly blog summary: ${error}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
exports.generateBlogIndex = generateBlogIndex;
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { Logger } from '../utils/logger.mjs';
|
|
4
|
+
import { readJsonFile } from '../utils/file-scanner.mjs';
|
|
5
|
+
|
|
6
|
+
function parseFrontmatter(fileContent) {
|
|
7
|
+
const frontmatter = {};
|
|
8
|
+
const match = fileContent.match(/^---([\s\S]*?)---/);
|
|
9
|
+
if (match && match[1]) {
|
|
10
|
+
const lines = match[1].trim().split('\n');
|
|
11
|
+
for (const line of lines) {
|
|
12
|
+
const [key, ...valueParts] = line.split(':');
|
|
13
|
+
if (key && valueParts.length > 0) {
|
|
14
|
+
const value = valueParts.join(':').trim();
|
|
15
|
+
if (key.trim() === 'title')
|
|
16
|
+
frontmatter.title = value;
|
|
17
|
+
if (key.trim() === 'description')
|
|
18
|
+
frontmatter.description = value;
|
|
19
|
+
if (key.trim() === 'icon')
|
|
20
|
+
frontmatter.icon = value;
|
|
21
|
+
if (key.trim() === 'date')
|
|
22
|
+
frontmatter.date = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return frontmatter;
|
|
27
|
+
}
|
|
28
|
+
function getIconComponentString(iconName) {
|
|
29
|
+
if (!iconName)
|
|
30
|
+
return undefined;
|
|
31
|
+
return `<${iconName} />`;
|
|
32
|
+
}
|
|
33
|
+
function getCurrentDateString() {
|
|
34
|
+
const now = new Date();
|
|
35
|
+
const year = now.getFullYear();
|
|
36
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
37
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
38
|
+
return `${year}-${month}-${day}`;
|
|
39
|
+
}
|
|
40
|
+
function updateFrontmatterDate(frontmatter) {
|
|
41
|
+
const currentDate = getCurrentDateString();
|
|
42
|
+
// Check if date field exists
|
|
43
|
+
if (frontmatter.includes('date:')) {
|
|
44
|
+
// Replace existing date
|
|
45
|
+
return frontmatter.replace(/date:\s*[^\n]*/, `date: ${currentDate}`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Add date field before the closing ---
|
|
49
|
+
return frontmatter.replace(/---$/, `date: ${currentDate}\n---`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function getBlogPrefix(config) {
|
|
53
|
+
if (config.blog?.prefix === undefined || config.blog?.prefix === null) {
|
|
54
|
+
return 'blog';
|
|
55
|
+
}
|
|
56
|
+
else if (typeof config.blog?.prefix === 'string' && config.blog?.prefix.trim() === '') {
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return String(config.blog.prefix).trim();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function getAllBlogArticles(blogDir, cwd, logger) {
|
|
64
|
+
const articles = [];
|
|
65
|
+
const blogPath = join(cwd, blogDir);
|
|
66
|
+
try {
|
|
67
|
+
const files = readdirSync(blogPath);
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
if (file.endsWith('.mdx') && file !== 'index.mdx') {
|
|
70
|
+
const slug = file.replace(/\.mdx$/, '');
|
|
71
|
+
const filePath = join(blogPath, file);
|
|
72
|
+
try {
|
|
73
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
74
|
+
const fm = parseFrontmatter(content);
|
|
75
|
+
if (!fm.title) {
|
|
76
|
+
logger.warn(`Article "${file}" is missing a title in its frontmatter. Skipping.`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
articles.push({
|
|
80
|
+
slug,
|
|
81
|
+
title: fm.title,
|
|
82
|
+
description: fm.description,
|
|
83
|
+
frontmatterIcon: fm.icon,
|
|
84
|
+
date: fm.date,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (readError) {
|
|
88
|
+
logger.warn(`Could not read or parse frontmatter for "${file}": ${readError}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (dirError) {
|
|
94
|
+
logger.error(`Could not read blog directory: ${dirError}`);
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
return articles;
|
|
98
|
+
}
|
|
99
|
+
async function generateBlogIndex(config, cwd = typeof process !== 'undefined' ? process.cwd() : '.') {
|
|
100
|
+
const logger = new Logger(config);
|
|
101
|
+
logger.warn('==============================');
|
|
102
|
+
logger.warn(`‼️ Current working directory: ⭕ ${cwd} ⭕`);
|
|
103
|
+
logger.warn('==============================');
|
|
104
|
+
try {
|
|
105
|
+
if (!config.blog) {
|
|
106
|
+
logger.error('Blog configuration is missing. Please configure blog settings.');
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
logger.log('Starting to generate blog index...');
|
|
110
|
+
const blogPath = join(cwd, config.blog.mdxDir);
|
|
111
|
+
const indexFile = join(blogPath, config.blog.outputFile || 'index.mdx');
|
|
112
|
+
const metaFile = join(blogPath, config.blog.metaFile || 'meta.json');
|
|
113
|
+
const iocFile = join(blogPath, `${config.blog.iocSlug || 'ioc'}.mdx`);
|
|
114
|
+
const iocSlug = config.blog.iocSlug || 'ioc';
|
|
115
|
+
const blogPrefix = getBlogPrefix(config);
|
|
116
|
+
let meta = { pages: [] };
|
|
117
|
+
const metaContent = readJsonFile(metaFile);
|
|
118
|
+
if (metaContent) {
|
|
119
|
+
meta = metaContent;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
logger.warn(`Could not read or parse ${metaFile}. No articles will be marked as featured.`);
|
|
123
|
+
}
|
|
124
|
+
// 统一提取被隐藏的 slug
|
|
125
|
+
const hiddenSlugs = new Set(meta.pages.filter(p => p.startsWith('!')).map(p => p.slice(1)));
|
|
126
|
+
// ioc 相关处理
|
|
127
|
+
const featuredSlugs = meta.pages
|
|
128
|
+
.filter(p => !p.startsWith('!'))
|
|
129
|
+
.map(p => p.endsWith('.mdx') ? p.slice(0, -4) : p)
|
|
130
|
+
.filter(slug => slug !== 'index' && slug !== '...');
|
|
131
|
+
logger.log(`Featured slugs (meta-config): ${featuredSlugs.join(', ')}`);
|
|
132
|
+
const allArticles = await getAllBlogArticles(config.blog.mdxDir, cwd, logger);
|
|
133
|
+
logger.log(`Found ${allArticles.length} all articles.`);
|
|
134
|
+
// 过滤所有被隐藏的 slug
|
|
135
|
+
const visibleArticles = allArticles.filter(a => !hiddenSlugs.has(a.slug));
|
|
136
|
+
// ioc article 处理
|
|
137
|
+
const iocArticle = visibleArticles.find(a => a.slug === iocSlug);
|
|
138
|
+
const filteredArticles = visibleArticles.filter(a => a.slug !== iocSlug);
|
|
139
|
+
if (filteredArticles.length === 0 && featuredSlugs.length === 0) {
|
|
140
|
+
logger.warn("No articles found or featured. The generated index might be empty or minimal.");
|
|
141
|
+
}
|
|
142
|
+
const featuredArticles = [];
|
|
143
|
+
const pastArticles = [];
|
|
144
|
+
filteredArticles.forEach(article => {
|
|
145
|
+
if (featuredSlugs.includes(article.slug)) {
|
|
146
|
+
featuredArticles.push(article);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
pastArticles.push(article);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// Sort articles by date in descending order (newest first)
|
|
153
|
+
const sortByDateDesc = (a, b) => {
|
|
154
|
+
if (a.date && b.date) {
|
|
155
|
+
return b.date.localeCompare(a.date); // Newest first
|
|
156
|
+
}
|
|
157
|
+
if (a.date)
|
|
158
|
+
return -1; // Articles with date come before those without
|
|
159
|
+
if (b.date)
|
|
160
|
+
return 1; // Articles with date come before those without
|
|
161
|
+
return 0; // Keep original order if both lack dates
|
|
162
|
+
};
|
|
163
|
+
featuredArticles.sort(sortByDateDesc);
|
|
164
|
+
pastArticles.sort(sortByDateDesc);
|
|
165
|
+
logger.log(`Found ${featuredArticles.length} featured articles (sorted by date).`);
|
|
166
|
+
logger.log(`Found ${pastArticles.length} past articles (sorted by date).`);
|
|
167
|
+
// Preserve existing frontmatter or use a default
|
|
168
|
+
let currentFileFrontmatter = '---\ntitle: Blog\ndescription: Articles and thoughts about various topics.\nicon: Rss\n---';
|
|
169
|
+
try {
|
|
170
|
+
const currentIndexContent = readFileSync(indexFile, 'utf-8');
|
|
171
|
+
const frontmatterMatch = currentIndexContent.match(/^---([\s\S]*?)---/);
|
|
172
|
+
if (frontmatterMatch && frontmatterMatch[0]) {
|
|
173
|
+
currentFileFrontmatter = frontmatterMatch[0];
|
|
174
|
+
logger.log('Preserving existing frontmatter from index.mdx');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
logger.warn('Could not read existing index.mdx or parse its frontmatter. Using default frontmatter.');
|
|
179
|
+
}
|
|
180
|
+
// Update date field in frontmatter
|
|
181
|
+
currentFileFrontmatter = updateFrontmatterDate(currentFileFrontmatter);
|
|
182
|
+
let mdxContent = `${currentFileFrontmatter}\n\n`;
|
|
183
|
+
const createCard = (article) => {
|
|
184
|
+
const iconString = getIconComponentString(article.frontmatterIcon);
|
|
185
|
+
const iconProp = iconString ? `icon={${iconString}}` : '';
|
|
186
|
+
// Escape only double quotes in title for JSX attribute
|
|
187
|
+
const escapedTitle = (article.title || '').replace(/"/g, '"');
|
|
188
|
+
// Content of the card - should be raw, as it might be MDX
|
|
189
|
+
const cardContent = article.date || article.description || '';
|
|
190
|
+
// Ensure there's a space before href if iconProp is present and not empty
|
|
191
|
+
const finalIconProp = iconProp ? `${iconProp} ` : '';
|
|
192
|
+
// refer path is /locale/blog, this is blog root dir, so here is blog/X, then you'll get /locale/blog/X
|
|
193
|
+
const href = blogPrefix ? `${blogPrefix}/${article.slug}` : `${article.slug}`;
|
|
194
|
+
return ` <ZiaCard ${finalIconProp} href="${href}" title="${escapedTitle}">\n ${cardContent}\n </ZiaCard>\n`;
|
|
195
|
+
};
|
|
196
|
+
if (featuredArticles.length > 0) {
|
|
197
|
+
mdxContent += `## Feature List\n\n<Cards>\n`;
|
|
198
|
+
featuredArticles.forEach(article => { mdxContent += createCard(article); });
|
|
199
|
+
mdxContent += `</Cards>\n\n`;
|
|
200
|
+
}
|
|
201
|
+
if (pastArticles.length > 0) {
|
|
202
|
+
mdxContent += `## Past List\n\n<Cards>\n`;
|
|
203
|
+
pastArticles.forEach(article => { mdxContent += createCard(article); });
|
|
204
|
+
mdxContent += `</Cards>\n`;
|
|
205
|
+
}
|
|
206
|
+
// add Monthly Summary block separately
|
|
207
|
+
if (iocArticle) {
|
|
208
|
+
mdxContent += `\n## Monthly Summary\n\n<Cards>\n`;
|
|
209
|
+
const iocHref = blogPrefix ? `${blogPrefix}/${iocSlug}` : `${iocSlug}`;
|
|
210
|
+
mdxContent += ` <ZiaCard href="${iocHref}" title="Overview">\n ${getCurrentDateString()}\n </ZiaCard>\n`;
|
|
211
|
+
mdxContent += `</Cards>\n`;
|
|
212
|
+
}
|
|
213
|
+
if (featuredArticles.length === 0 && pastArticles.length === 0 && !iocArticle) {
|
|
214
|
+
mdxContent += "## Ooops\nNo blog posts found yet. Stay tuned!\n";
|
|
215
|
+
}
|
|
216
|
+
writeFileSync(indexFile, mdxContent);
|
|
217
|
+
logger.success(`Successfully generated ${indexFile}`);
|
|
218
|
+
// generate monthly statistics
|
|
219
|
+
await generateMonthlyBlogSummary(config, visibleArticles, iocFile, iocSlug, logger);
|
|
220
|
+
logger.log('Blog index generation completed successfully!');
|
|
221
|
+
logger.saveToFile('generate-blog.log', cwd);
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
logger.error(`Error generating blog index: ${error}`);
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* generate blog monthly statistics details
|
|
231
|
+
*/
|
|
232
|
+
async function generateMonthlyBlogSummary(config, articles, iocFile, iocSlug, logger) {
|
|
233
|
+
try {
|
|
234
|
+
// filter out articles without date and slug is ioc
|
|
235
|
+
const articlesWithDate = articles.filter(a => a.date && a.slug !== iocSlug);
|
|
236
|
+
// group by month
|
|
237
|
+
const monthMap = {};
|
|
238
|
+
for (const art of articlesWithDate) {
|
|
239
|
+
// only take the first 7 digits yyyy-mm
|
|
240
|
+
const month = art.date.slice(0, 7);
|
|
241
|
+
if (!monthMap[month])
|
|
242
|
+
monthMap[month] = [];
|
|
243
|
+
monthMap[month].push({ date: art.date, title: art.title, slug: art.slug });
|
|
244
|
+
}
|
|
245
|
+
// sort months in descending order
|
|
246
|
+
const sortedMonths = Object.keys(monthMap).sort((a, b) => b.localeCompare(a));
|
|
247
|
+
// sort articles by date in descending order
|
|
248
|
+
for (const month of sortedMonths) {
|
|
249
|
+
monthMap[month].sort((a, b) => b.date.localeCompare(a.date));
|
|
250
|
+
}
|
|
251
|
+
// read ioc.mdx original frontmatter
|
|
252
|
+
let frontmatter = '';
|
|
253
|
+
try {
|
|
254
|
+
const content = readFileSync(iocFile, 'utf-8');
|
|
255
|
+
const match = content.match(/^---([\s\S]*?)---/);
|
|
256
|
+
if (match && match[0])
|
|
257
|
+
frontmatter = match[0];
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// File doesn't exist, use default
|
|
261
|
+
}
|
|
262
|
+
// if there is no frontmatter, use the default
|
|
263
|
+
if (!frontmatter) {
|
|
264
|
+
frontmatter = '---\ntitle: Monthly Summary\ndescription: Index and Summary\n---';
|
|
265
|
+
}
|
|
266
|
+
// update date field in frontmatter
|
|
267
|
+
frontmatter = updateFrontmatterDate(frontmatter);
|
|
268
|
+
// generate content
|
|
269
|
+
let mdx = `${frontmatter}\n\n\n## Overview\n<Files>\n`;
|
|
270
|
+
if (sortedMonths.length === 0) {
|
|
271
|
+
mdx += ' <ZiaFile name="Comming Soon" className="opacity-50" disabled/>\n';
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
for (const month of sortedMonths) {
|
|
275
|
+
// Folder name format YYYY-MM(article count)
|
|
276
|
+
const count = monthMap[month].length;
|
|
277
|
+
const folderTitle = `${month}(${count})`;
|
|
278
|
+
// default open the latest month
|
|
279
|
+
const defaultOpen = month === sortedMonths[0] ? ' defaultOpen' : '';
|
|
280
|
+
mdx += ` <ZiaFolder name="${folderTitle}"${defaultOpen}>\n`;
|
|
281
|
+
for (const art of monthMap[month]) {
|
|
282
|
+
// File name="YYYY-MM-DD(Title)" format
|
|
283
|
+
const day = art.date.slice(0, 10);
|
|
284
|
+
// refer path is /locale/blog/ioc, so here is ./X, then you'll get /locale/blog/X
|
|
285
|
+
const href = art.slug ? `./${art.slug}` : '';
|
|
286
|
+
mdx += ` <ZiaFile name="${day}(${art.title})" href="${href}" />\n`;
|
|
287
|
+
}
|
|
288
|
+
mdx += ` </ZiaFolder>\n`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
mdx += '</Files>\n\n';
|
|
292
|
+
writeFileSync(iocFile, mdx);
|
|
293
|
+
logger.success(`Successfully generated Monthly Blog Summary: ${iocFile}`);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
logger.error(`Error generating monthly blog summary: ${error}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export { generateBlogIndex };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-nextjs-architecture.d.ts","sourceRoot":"","sources":["../../src/commands/generate-nextjs-architecture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAkB,MAAM,4BAA4B,CAAA;AAe7E,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,gBAAgB,EACxB,GAAG,GAAE,MAA6D,GACjE,OAAO,CAAC,MAAM,CAAC,CAgEjB"}
|