@litodocs/cli 0.5.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/LICENSE +21 -0
- package/README.md +260 -0
- package/bin/cli.js +8 -0
- package/package.json +50 -0
- package/src/cli.js +116 -0
- package/src/commands/build.js +105 -0
- package/src/commands/dev.js +133 -0
- package/src/commands/eject.js +89 -0
- package/src/commands/template.js +80 -0
- package/src/core/astro.js +9 -0
- package/src/core/colors.js +142 -0
- package/src/core/config-sync.js +117 -0
- package/src/core/config.js +94 -0
- package/src/core/output.js +15 -0
- package/src/core/package-manager.js +96 -0
- package/src/core/providers.js +78 -0
- package/src/core/scaffold.js +53 -0
- package/src/core/sync.js +364 -0
- package/src/core/template-fetcher.js +242 -0
- package/src/core/template-registry.js +50 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import pkg from 'fs-extra';
|
|
3
|
+
const { writeFile, readFile, pathExists } = pkg;
|
|
4
|
+
import { installPackage } from './package-manager.js';
|
|
5
|
+
|
|
6
|
+
export async function configureProvider(projectDir, provider, rendering = 'static') {
|
|
7
|
+
// Cloudflare
|
|
8
|
+
if (provider === 'cloudflare') {
|
|
9
|
+
if (rendering !== 'static') {
|
|
10
|
+
await installPackage(projectDir, '@astrojs/cloudflare');
|
|
11
|
+
await updateAstroConfig(projectDir, 'cloudflare', rendering);
|
|
12
|
+
}
|
|
13
|
+
// Cloudflare usually requires a _routes.json or wrangler.toml, but the adapter handles the build output.
|
|
14
|
+
// We can add a basic wrangler.toml if needed, but often not required for Pages.
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Vercel
|
|
18
|
+
else if (provider === 'vercel') {
|
|
19
|
+
if (rendering !== 'static') {
|
|
20
|
+
await installPackage(projectDir, '@astrojs/vercel');
|
|
21
|
+
await updateAstroConfig(projectDir, 'vercel', rendering);
|
|
22
|
+
}
|
|
23
|
+
const configPath = join(projectDir, 'vercel.json');
|
|
24
|
+
if (!await pathExists(configPath)) {
|
|
25
|
+
const content = {
|
|
26
|
+
"cleanUrls": true,
|
|
27
|
+
"framework": "astro",
|
|
28
|
+
"buildCommand": "lito build",
|
|
29
|
+
"outputDirectory": "dist"
|
|
30
|
+
};
|
|
31
|
+
await writeFile(configPath, JSON.stringify(content, null, 2), 'utf-8');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Netlify
|
|
36
|
+
else if (provider === 'netlify') {
|
|
37
|
+
if (rendering !== 'static') {
|
|
38
|
+
await installPackage(projectDir, '@astrojs/netlify');
|
|
39
|
+
await updateAstroConfig(projectDir, 'netlify', rendering);
|
|
40
|
+
}
|
|
41
|
+
const configPath = join(projectDir, 'netlify.toml');
|
|
42
|
+
if (!await pathExists(configPath)) {
|
|
43
|
+
const content = `[build]
|
|
44
|
+
publish = "dist"
|
|
45
|
+
command = "lito build"
|
|
46
|
+
|
|
47
|
+
[[headers]]
|
|
48
|
+
for = "/*"
|
|
49
|
+
[headers.values]
|
|
50
|
+
X-Frame-Options = "DENY"
|
|
51
|
+
X-XSS-Protection = "1; mode=block"
|
|
52
|
+
`;
|
|
53
|
+
await writeFile(configPath, content, 'utf-8');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function updateAstroConfig(projectDir, adapterName, rendering) {
|
|
59
|
+
const configPath = join(projectDir, 'astro.config.mjs');
|
|
60
|
+
let content = await readFile(configPath, 'utf-8');
|
|
61
|
+
|
|
62
|
+
// Add import
|
|
63
|
+
const importStatement = `import ${adapterName} from '@astrojs/${adapterName}';\n`;
|
|
64
|
+
if (!content.includes(`@astrojs/${adapterName}`)) {
|
|
65
|
+
content = importStatement + content;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Add adapter and output to defineConfig
|
|
69
|
+
// We look for "export default defineConfig({"
|
|
70
|
+
if (content.includes('export default defineConfig({') && !content.includes('output:')) {
|
|
71
|
+
content = content.replace(
|
|
72
|
+
'export default defineConfig({\n',
|
|
73
|
+
`export default defineConfig({\n adapter: ${adapterName}(),\n output: '${rendering}',`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await writeFile(configPath, content, 'utf-8');
|
|
78
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import pkg from 'fs-extra';
|
|
2
|
+
const { ensureDir, emptyDir, copy, remove } = pkg;
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join, basename } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// Use a consistent directory path instead of random temp directories
|
|
12
|
+
const LITO_DIR = join(tmpdir(), '.lito');
|
|
13
|
+
|
|
14
|
+
export async function scaffoldProject(customTemplatePath = null) {
|
|
15
|
+
// Ensure the directory exists (creates if it doesn't)
|
|
16
|
+
await ensureDir(LITO_DIR);
|
|
17
|
+
|
|
18
|
+
// Empty the directory to ensure a clean state
|
|
19
|
+
await emptyDir(LITO_DIR);
|
|
20
|
+
|
|
21
|
+
const tempDir = LITO_DIR;
|
|
22
|
+
|
|
23
|
+
// Use custom template path if provided, otherwise use bundled template
|
|
24
|
+
const templatePath = customTemplatePath || join(__dirname, '../template');
|
|
25
|
+
await copy(templatePath, tempDir, {
|
|
26
|
+
filter: (src) => {
|
|
27
|
+
const name = basename(src);
|
|
28
|
+
|
|
29
|
+
// Exclude node_modules directory
|
|
30
|
+
if (name === 'node_modules') return false;
|
|
31
|
+
|
|
32
|
+
// Exclude lock files
|
|
33
|
+
if (name === 'pnpm-lock.yaml' || name === 'bun.lock' ||
|
|
34
|
+
name === 'package-lock.json' || name === 'yarn.lock') return false;
|
|
35
|
+
|
|
36
|
+
// Exclude .astro cache directory (but NOT .astro component files)
|
|
37
|
+
if (name === '.astro') return false;
|
|
38
|
+
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return tempDir;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Cleanup function to remove the temp directory on exit
|
|
47
|
+
export async function cleanupProject() {
|
|
48
|
+
try {
|
|
49
|
+
await remove(LITO_DIR);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Ignore errors during cleanup (directory might not exist)
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/core/sync.js
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import pkg from 'fs-extra';
|
|
2
|
+
const { copy, ensureDir, remove, readFile, writeFile, pathExists } = pkg;
|
|
3
|
+
import { join, relative, sep } from 'path';
|
|
4
|
+
import { readdir, stat } from 'fs/promises';
|
|
5
|
+
|
|
6
|
+
// Known locale codes (ISO 639-1)
|
|
7
|
+
const KNOWN_LOCALES = [
|
|
8
|
+
'en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'zh', 'ja', 'ko',
|
|
9
|
+
'ar', 'hi', 'bn', 'pa', 'id', 'ms', 'th', 'vi', 'tr', 'pl',
|
|
10
|
+
'nl', 'sv', 'da', 'no', 'fi', 'cs', 'sk', 'hu', 'ro', 'bg',
|
|
11
|
+
'uk', 'he', 'fa', 'ur', 'ta', 'te', 'mr', 'gu', 'kn', 'ml',
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// Special folders that are not content
|
|
15
|
+
const SPECIAL_FOLDERS = ['_assets', '_css', '_images', '_static', 'public'];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get i18n configuration from docs-config.json
|
|
19
|
+
*/
|
|
20
|
+
async function getI18nConfig(sourcePath) {
|
|
21
|
+
const configPath = join(sourcePath, 'docs-config.json');
|
|
22
|
+
if (await pathExists(configPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const config = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
25
|
+
return config.i18n || { defaultLocale: 'en', locales: ['en'] };
|
|
26
|
+
} catch {
|
|
27
|
+
return { defaultLocale: 'en', locales: ['en'] };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { defaultLocale: 'en', locales: ['en'] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get versioning configuration from docs-config.json
|
|
35
|
+
*/
|
|
36
|
+
async function getVersioningConfig(sourcePath) {
|
|
37
|
+
const configPath = join(sourcePath, 'docs-config.json');
|
|
38
|
+
if (await pathExists(configPath)) {
|
|
39
|
+
try {
|
|
40
|
+
const config = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
41
|
+
return config.versioning || { enabled: false };
|
|
42
|
+
} catch {
|
|
43
|
+
return { enabled: false };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { enabled: false };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Detect locale folders in the docs directory
|
|
51
|
+
*/
|
|
52
|
+
async function detectLocaleFolders(sourcePath, configuredLocales) {
|
|
53
|
+
const detectedLocales = [];
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const entries = await readdir(sourcePath, { withFileTypes: true });
|
|
57
|
+
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
const folderName = entry.name.toLowerCase();
|
|
61
|
+
// Check if it's a known locale or configured locale
|
|
62
|
+
if (KNOWN_LOCALES.includes(folderName) || configuredLocales.includes(folderName)) {
|
|
63
|
+
detectedLocales.push(entry.name);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// Ignore errors
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return detectedLocales;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Detect version folders based on versioning config
|
|
76
|
+
*/
|
|
77
|
+
async function detectVersionFolders(sourcePath, versioningConfig) {
|
|
78
|
+
if (!versioningConfig?.enabled || !versioningConfig?.versions?.length) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const versionFolders = [];
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const entries = await readdir(sourcePath, { withFileTypes: true });
|
|
86
|
+
const configuredPaths = versioningConfig.versions.map(v => v.path);
|
|
87
|
+
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
if (entry.isDirectory() && configuredPaths.includes(entry.name)) {
|
|
90
|
+
versionFolders.push(entry.name);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// Ignore errors
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return versionFolders;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function syncDocs(sourcePath, projectDir) {
|
|
101
|
+
const targetPath = join(projectDir, 'src', 'pages');
|
|
102
|
+
|
|
103
|
+
// Clear existing pages (except index if it exists in template)
|
|
104
|
+
await ensureDir(targetPath);
|
|
105
|
+
|
|
106
|
+
// Get i18n configuration
|
|
107
|
+
const i18nConfig = await getI18nConfig(sourcePath);
|
|
108
|
+
const configuredLocales = i18nConfig.locales || ['en'];
|
|
109
|
+
const defaultLocale = i18nConfig.defaultLocale || 'en';
|
|
110
|
+
|
|
111
|
+
// Get versioning configuration
|
|
112
|
+
const versioningConfig = await getVersioningConfig(sourcePath);
|
|
113
|
+
const versionFolders = await detectVersionFolders(sourcePath, versioningConfig);
|
|
114
|
+
|
|
115
|
+
// Detect locale folders
|
|
116
|
+
const localeFolders = await detectLocaleFolders(sourcePath, configuredLocales);
|
|
117
|
+
|
|
118
|
+
// Combine folders to exclude from root sync
|
|
119
|
+
const excludeFolders = [...localeFolders, ...versionFolders];
|
|
120
|
+
|
|
121
|
+
// Copy default content (root level, excluding locale and version folders)
|
|
122
|
+
await copy(sourcePath, targetPath, {
|
|
123
|
+
overwrite: true,
|
|
124
|
+
filter: (src) => {
|
|
125
|
+
const relativePath = relative(sourcePath, src);
|
|
126
|
+
const firstSegment = relativePath.split(sep)[0];
|
|
127
|
+
|
|
128
|
+
// Exclude special folders
|
|
129
|
+
if (SPECIAL_FOLDERS.some(folder => relativePath.startsWith(folder))) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Exclude locale and version folders (they'll be synced separately)
|
|
134
|
+
if (excludeFolders.includes(firstSegment)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Exclude config files
|
|
139
|
+
if (relativePath === 'docs-config.json' || relativePath === 'vercel.json') {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Only copy markdown/mdx files and directories
|
|
144
|
+
return src.endsWith('.md') || src.endsWith('.mdx') || !src.includes('.');
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Sync version folders
|
|
149
|
+
await Promise.all(versionFolders.map(async (version) => {
|
|
150
|
+
const versionSourcePath = join(sourcePath, version);
|
|
151
|
+
const versionTargetPath = join(targetPath, version);
|
|
152
|
+
|
|
153
|
+
await ensureDir(versionTargetPath);
|
|
154
|
+
await copy(versionSourcePath, versionTargetPath, {
|
|
155
|
+
overwrite: true,
|
|
156
|
+
filter: (src) => {
|
|
157
|
+
// Only copy markdown/mdx files and directories
|
|
158
|
+
return src.endsWith('.md') || src.endsWith('.mdx') || !src.includes('.');
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Inject layout into version markdown files
|
|
163
|
+
await injectLayoutIntoMarkdown(versionTargetPath, targetPath, null, [], version);
|
|
164
|
+
}));
|
|
165
|
+
|
|
166
|
+
// Sync locale folders
|
|
167
|
+
await Promise.all(localeFolders.map(async (locale) => {
|
|
168
|
+
const localeSourcePath = join(sourcePath, locale);
|
|
169
|
+
const localeTargetPath = join(targetPath, locale);
|
|
170
|
+
|
|
171
|
+
await ensureDir(localeTargetPath);
|
|
172
|
+
await copy(localeSourcePath, localeTargetPath, {
|
|
173
|
+
overwrite: true,
|
|
174
|
+
filter: (src) => {
|
|
175
|
+
// Only copy markdown/mdx files and directories
|
|
176
|
+
return src.endsWith('.md') || src.endsWith('.mdx') || !src.includes('.');
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Inject layout into locale markdown files
|
|
181
|
+
await injectLayoutIntoMarkdown(localeTargetPath, targetPath, locale);
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
// Inject layout into default locale markdown files
|
|
185
|
+
await injectLayoutIntoMarkdown(targetPath, targetPath, null, excludeFolders);
|
|
186
|
+
|
|
187
|
+
// Check for custom landing page conflict
|
|
188
|
+
const hasUserIndex = ['index.md', 'index.mdx'].some(file =>
|
|
189
|
+
pkg.existsSync(join(targetPath, file))
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (hasUserIndex) {
|
|
193
|
+
const defaultIndexAstro = join(targetPath, 'index.astro');
|
|
194
|
+
if (pkg.existsSync(defaultIndexAstro)) {
|
|
195
|
+
await remove(defaultIndexAstro);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Sync user assets (images, css, static files)
|
|
200
|
+
await syncUserAssets(sourcePath, projectDir);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Sync user assets from docs folder to the Astro project
|
|
205
|
+
*/
|
|
206
|
+
async function syncUserAssets(sourcePath, projectDir) {
|
|
207
|
+
const tasks = [];
|
|
208
|
+
|
|
209
|
+
// Sync _assets folder to public/assets
|
|
210
|
+
tasks.push((async () => {
|
|
211
|
+
const assetsSource = join(sourcePath, '_assets');
|
|
212
|
+
const assetsTarget = join(projectDir, 'public', 'assets');
|
|
213
|
+
if (await pathExists(assetsSource)) {
|
|
214
|
+
await ensureDir(assetsTarget);
|
|
215
|
+
await copy(assetsSource, assetsTarget, { overwrite: true });
|
|
216
|
+
}
|
|
217
|
+
})());
|
|
218
|
+
|
|
219
|
+
// Sync _images folder to public/images
|
|
220
|
+
tasks.push((async () => {
|
|
221
|
+
const imagesSource = join(sourcePath, '_images');
|
|
222
|
+
const imagesTarget = join(projectDir, 'public', 'images');
|
|
223
|
+
if (await pathExists(imagesSource)) {
|
|
224
|
+
await ensureDir(imagesTarget);
|
|
225
|
+
await copy(imagesSource, imagesTarget, { overwrite: true });
|
|
226
|
+
}
|
|
227
|
+
})());
|
|
228
|
+
|
|
229
|
+
// Sync public folder to public root
|
|
230
|
+
tasks.push((async () => {
|
|
231
|
+
const publicSource = join(sourcePath, 'public');
|
|
232
|
+
const publicTarget = join(projectDir, 'public');
|
|
233
|
+
if (await pathExists(publicSource)) {
|
|
234
|
+
await ensureDir(publicTarget);
|
|
235
|
+
await copy(publicSource, publicTarget, { overwrite: true });
|
|
236
|
+
}
|
|
237
|
+
})());
|
|
238
|
+
|
|
239
|
+
// Sync _css folder for custom styles
|
|
240
|
+
tasks.push((async () => {
|
|
241
|
+
const cssSource = join(sourcePath, '_css');
|
|
242
|
+
const cssTarget = join(projectDir, 'src', 'styles');
|
|
243
|
+
if (await pathExists(cssSource)) {
|
|
244
|
+
await ensureDir(cssTarget);
|
|
245
|
+
|
|
246
|
+
const cssTasks = [];
|
|
247
|
+
|
|
248
|
+
// Copy custom.css if it exists
|
|
249
|
+
const customCssSource = join(cssSource, 'custom.css');
|
|
250
|
+
cssTasks.push((async () => {
|
|
251
|
+
if (await pathExists(customCssSource)) {
|
|
252
|
+
await copy(customCssSource, join(cssTarget, 'custom.css'), { overwrite: true });
|
|
253
|
+
}
|
|
254
|
+
})());
|
|
255
|
+
|
|
256
|
+
// Copy theme.css override if it exists
|
|
257
|
+
const themeOverrideSource = join(cssSource, 'theme.css');
|
|
258
|
+
cssTasks.push((async () => {
|
|
259
|
+
if (await pathExists(themeOverrideSource)) {
|
|
260
|
+
await copy(themeOverrideSource, join(cssTarget, 'theme.css'), { overwrite: true });
|
|
261
|
+
}
|
|
262
|
+
})());
|
|
263
|
+
|
|
264
|
+
// Copy any additional CSS files
|
|
265
|
+
cssTasks.push((async () => {
|
|
266
|
+
const cssFiles = await readdir(cssSource).catch(() => []);
|
|
267
|
+
await Promise.all(cssFiles.map(async (file) => {
|
|
268
|
+
if (file.endsWith('.css') && file !== 'custom.css' && file !== 'theme.css') {
|
|
269
|
+
await copy(join(cssSource, file), join(cssTarget, file), { overwrite: true });
|
|
270
|
+
}
|
|
271
|
+
}));
|
|
272
|
+
})());
|
|
273
|
+
|
|
274
|
+
await Promise.all(cssTasks);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Create/update custom.css import in global.css if custom.css exists
|
|
278
|
+
const customCssPath = join(cssTarget, 'custom.css');
|
|
279
|
+
const globalCssPath = join(cssTarget, 'global.css');
|
|
280
|
+
|
|
281
|
+
if (await pathExists(customCssPath) && await pathExists(globalCssPath)) {
|
|
282
|
+
let globalCss = await readFile(globalCssPath, 'utf-8');
|
|
283
|
+
const customImport = '@import "./custom.css";';
|
|
284
|
+
|
|
285
|
+
if (!globalCss.includes(customImport)) {
|
|
286
|
+
globalCss = globalCss.trimEnd() + '\n\n/* User custom styles */\n' + customImport + '\n';
|
|
287
|
+
await writeFile(globalCssPath, globalCss, 'utf-8');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
})());
|
|
291
|
+
|
|
292
|
+
await Promise.all(tasks);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Inject layout frontmatter into markdown files
|
|
297
|
+
* @param {string} dir - Directory to process
|
|
298
|
+
* @param {string} rootDir - Root pages directory
|
|
299
|
+
* @param {string|null} locale - Current locale (null for default)
|
|
300
|
+
* @param {string[]} skipFolders - Folders to skip (locale folders when processing root)
|
|
301
|
+
* @param {string|null} version - Current version (null for non-versioned)
|
|
302
|
+
*/
|
|
303
|
+
async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders = [], version = null) {
|
|
304
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
305
|
+
|
|
306
|
+
await Promise.all(entries.map(async (entry) => {
|
|
307
|
+
const fullPath = join(dir, entry.name);
|
|
308
|
+
|
|
309
|
+
if (entry.isDirectory()) {
|
|
310
|
+
// Skip locale folders when processing root
|
|
311
|
+
if (skipFolders.includes(entry.name)) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
await injectLayoutIntoMarkdown(fullPath, rootDir, locale, [], version);
|
|
315
|
+
} else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) {
|
|
316
|
+
let content = await readFile(fullPath, 'utf-8');
|
|
317
|
+
|
|
318
|
+
// Calculate relative path to layout
|
|
319
|
+
const relPath = relative(rootDir, fullPath);
|
|
320
|
+
const depth = relPath.split(sep).length - 1;
|
|
321
|
+
const layoutPath = '../'.repeat(depth + 1) + 'layouts/MarkdownLayout.astro';
|
|
322
|
+
|
|
323
|
+
// Check if file already has frontmatter
|
|
324
|
+
if (content.startsWith('---')) {
|
|
325
|
+
const endOfFrontmatter = content.indexOf('---', 3);
|
|
326
|
+
if (endOfFrontmatter !== -1) {
|
|
327
|
+
let frontmatterBlock = content.substring(3, endOfFrontmatter).trim();
|
|
328
|
+
const body = content.substring(endOfFrontmatter + 3);
|
|
329
|
+
|
|
330
|
+
// Determine which layout to use
|
|
331
|
+
let targetLayout = layoutPath;
|
|
332
|
+
if (frontmatterBlock.includes('api:')) {
|
|
333
|
+
targetLayout = '../'.repeat(depth + 1) + 'layouts/APILayout.astro';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Add locale to frontmatter if present
|
|
337
|
+
if (locale && !frontmatterBlock.includes('lang:')) {
|
|
338
|
+
frontmatterBlock = `lang: ${locale}\n${frontmatterBlock}`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Add version to frontmatter if present
|
|
342
|
+
if (version && !frontmatterBlock.includes('docVersion:')) {
|
|
343
|
+
frontmatterBlock = `docVersion: ${version}\n${frontmatterBlock}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Check if layout is already specified
|
|
347
|
+
if (!frontmatterBlock.includes('layout:')) {
|
|
348
|
+
content = `---\n${frontmatterBlock}\nlayout: ${targetLayout}\n---${body}`;
|
|
349
|
+
} else {
|
|
350
|
+
content = `---\n${frontmatterBlock}\n---${body}`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
// No frontmatter, add minimal frontmatter with layout
|
|
355
|
+
const title = entry.name.replace(/\.(md|mdx)$/, '').replace(/-/g, ' ');
|
|
356
|
+
const langLine = locale ? `lang: ${locale}\n` : '';
|
|
357
|
+
const versionLine = version ? `docVersion: ${version}\n` : '';
|
|
358
|
+
content = `---\n${langLine}${versionLine}title: ${title}\nlayout: ${layoutPath}\n---\n\n${content}`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
await writeFile(fullPath, content, 'utf-8');
|
|
362
|
+
}
|
|
363
|
+
}));
|
|
364
|
+
}
|