@windrun-huaiin/dev-scripts 6.8.2 → 6.9.1

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 (65) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +137 -1410
  4. package/dist/cli.mjs +145 -432
  5. package/dist/commands/check-translations.d.ts +3 -0
  6. package/dist/commands/check-translations.d.ts.map +1 -0
  7. package/dist/commands/check-translations.js +132 -0
  8. package/dist/commands/check-translations.mjs +130 -0
  9. package/dist/commands/clean-translations.d.ts +3 -0
  10. package/dist/commands/clean-translations.d.ts.map +1 -0
  11. package/dist/commands/clean-translations.js +148 -0
  12. package/dist/commands/clean-translations.mjs +146 -0
  13. package/dist/commands/create-diaomao-app.d.ts +2 -0
  14. package/dist/commands/create-diaomao-app.d.ts.map +1 -0
  15. package/dist/commands/create-diaomao-app.js +151 -0
  16. package/dist/commands/create-diaomao-app.mjs +149 -0
  17. package/dist/commands/deep-clean.d.ts +3 -0
  18. package/dist/commands/deep-clean.d.ts.map +1 -0
  19. package/dist/commands/deep-clean.js +119 -0
  20. package/dist/commands/deep-clean.mjs +117 -0
  21. package/dist/commands/easy-changeset.d.ts +2 -0
  22. package/dist/commands/easy-changeset.d.ts.map +1 -0
  23. package/dist/commands/easy-changeset.js +39 -0
  24. package/dist/commands/easy-changeset.mjs +37 -0
  25. package/dist/commands/generate-blog-index.d.ts +3 -0
  26. package/dist/commands/generate-blog-index.d.ts.map +1 -0
  27. package/dist/commands/generate-blog-index.js +302 -0
  28. package/dist/commands/generate-blog-index.mjs +300 -0
  29. package/dist/commands/generate-nextjs-architecture.d.ts +3 -0
  30. package/dist/commands/generate-nextjs-architecture.d.ts.map +1 -0
  31. package/dist/commands/generate-nextjs-architecture.js +84 -0
  32. package/dist/commands/generate-nextjs-architecture.mjs +82 -0
  33. package/dist/config/index.d.ts +10 -0
  34. package/dist/config/index.d.ts.map +1 -0
  35. package/dist/config/index.js +173 -0
  36. package/dist/config/index.mjs +170 -0
  37. package/dist/config/schema.d.ts +34 -0
  38. package/dist/config/schema.d.ts.map +1 -0
  39. package/dist/config/schema.js +80 -0
  40. package/dist/config/schema.mjs +78 -0
  41. package/dist/index.d.ts +6 -49
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +9 -996
  44. package/dist/index.mjs +4 -3
  45. package/dist/utils/file-scanner.d.ts +22 -0
  46. package/dist/utils/file-scanner.d.ts.map +1 -0
  47. package/dist/utils/file-scanner.js +70 -0
  48. package/dist/utils/file-scanner.mjs +65 -0
  49. package/dist/utils/logger.d.ts +24 -0
  50. package/dist/utils/logger.d.ts.map +1 -0
  51. package/dist/utils/logger.js +63 -0
  52. package/dist/utils/logger.mjs +61 -0
  53. package/dist/utils/translation-parser.d.ts +29 -0
  54. package/dist/utils/translation-parser.d.ts.map +1 -0
  55. package/dist/utils/translation-parser.js +225 -0
  56. package/dist/utils/translation-parser.mjs +218 -0
  57. package/package.json +5 -5
  58. package/dist/chunk-GVR6HFHM.mjs +0 -989
  59. package/dist/chunk-GVR6HFHM.mjs.map +0 -1
  60. package/dist/cli.d.mts +0 -1
  61. package/dist/cli.js.map +0 -1
  62. package/dist/cli.mjs.map +0 -1
  63. package/dist/index.d.mts +0 -49
  64. package/dist/index.js.map +0 -1
  65. 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, '&quot;');
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, '&quot;');
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,3 @@
1
+ import { DevScriptsConfig } from '@dev-scripts/config/schema';
2
+ export declare function generateNextjsArchitecture(config: DevScriptsConfig, cwd?: string): Promise<number>;
3
+ //# sourceMappingURL=generate-nextjs-architecture.d.ts.map
@@ -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"}