fossbook 0.2.15 → 0.2.16

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/bin/fossbook.js CHANGED
@@ -60,74 +60,85 @@ if (hasFlag("-h", "--help") || !command) {
60
60
 
61
61
  const configPath = getOption("-c", "--config");
62
62
 
63
- switch (command) {
64
- case "build": {
65
- const config = loadConfig(configPath);
66
- const outputOverride = getOption("-o", "--output");
67
- if (outputOverride) config.dev.outdir = outputOverride;
68
- config.includeDrafts = hasFlag("--include-drafts");
69
-
70
- const { build } = require("../lib/index");
71
- build(config);
72
- break;
73
- }
74
-
75
- case "serve": {
76
- const config = loadConfig(configPath);
77
- const outputOverride = getOption("-o", "--output");
78
- if (outputOverride) config.dev.outdir = outputOverride;
79
- config.includeDrafts = hasFlag("--include-drafts");
80
- const port = getOption("-p", "--port") || 3000;
63
+ async function run() {
64
+ switch (command) {
65
+ case "build": {
66
+ const config = loadConfig(configPath);
67
+ const outputOverride = getOption("-o", "--output");
68
+ if (outputOverride) config.dev.outdir = outputOverride;
69
+ config.includeDrafts = hasFlag("--include-drafts");
70
+
71
+ const { build } = require("../lib/index");
72
+ await build(config);
73
+ break;
74
+ }
81
75
 
82
- const { build } = require("../lib/index");
83
- build(config);
76
+ case "serve": {
77
+ const config = loadConfig(configPath);
78
+ const outputOverride = getOption("-o", "--output");
79
+ if (outputOverride) config.dev.outdir = outputOverride;
80
+ config.includeDrafts = hasFlag("--include-drafts");
81
+ const port = getOption("-p", "--port") || 3000;
84
82
 
85
- const { serve } = require("../lib/server");
86
- serve(config, Number(port));
87
- break;
88
- }
83
+ const { build } = require("../lib/index");
84
+ await build(config);
89
85
 
90
- case "new": {
91
- const title = args[1];
92
- if (!title) {
93
- console.error('Error: Please provide a post title. Usage: fossbook new "My Post Title"');
94
- process.exit(1);
86
+ const { serve } = require("../lib/server");
87
+ serve(config, Number(port));
88
+ break;
95
89
  }
96
- if (hasFlag("--lang") && !getOption("--lang")) {
97
- console.error("Error: Please provide at least one language after --lang");
98
- process.exit(1);
90
+
91
+ case "new": {
92
+ const title = args[1];
93
+ if (!title) {
94
+ console.error(
95
+ 'Error: Please provide a post title. Usage: fossbook new "My Post Title"',
96
+ );
97
+ process.exit(1);
98
+ }
99
+ if (hasFlag("--lang") && !getOption("--lang")) {
100
+ console.error(
101
+ "Error: Please provide at least one language after --lang",
102
+ );
103
+ process.exit(1);
104
+ }
105
+ const config = loadConfig(configPath);
106
+ const { createPost } = require("../lib/new");
107
+ try {
108
+ createPost(config, title, getOption("--lang"));
109
+ } catch (error) {
110
+ console.error(`Error: ${error.message}`);
111
+ process.exit(1);
112
+ }
113
+ break;
99
114
  }
100
- const config = loadConfig(configPath);
101
- const { createPost } = require("../lib/new");
102
- try {
103
- createPost(config, title, getOption("--lang"));
104
- } catch (error) {
105
- console.error(`Error: ${error.message}`);
106
- process.exit(1);
115
+
116
+ case "init": {
117
+ const { initProject } = require("../lib/init");
118
+ const projectName = !args[1] || args[1].startsWith("--") ? null : args[1];
119
+ initProject({ github: hasFlag("--github"), name: projectName });
120
+ break;
107
121
  }
108
- break;
109
- }
110
122
 
111
- case "init": {
112
- const { initProject } = require("../lib/init");
113
- const projectName = !args[1] || args[1].startsWith("--") ? null : args[1];
114
- initProject({ github: hasFlag("--github"), name: projectName });
115
- break;
116
- }
123
+ case "deploy": {
124
+ const config = loadConfig(configPath);
125
+ const { deploy } = require("../lib/deploy");
126
+ await deploy(config, {
127
+ message: getOption("-m", "--message"),
128
+ noWait: hasFlag("--no-wait"),
129
+ draft: hasFlag("--draft"),
130
+ });
131
+ break;
132
+ }
117
133
 
118
- case "deploy": {
119
- const config = loadConfig(configPath);
120
- const { deploy } = require("../lib/deploy");
121
- deploy(config, {
122
- message: getOption("-m", "--message"),
123
- noWait: hasFlag("--no-wait"),
124
- draft: hasFlag("--draft"),
125
- });
126
- break;
134
+ default:
135
+ console.error(`Unknown command: ${command}`);
136
+ printHelp();
137
+ process.exit(1);
127
138
  }
128
-
129
- default:
130
- console.error(`Unknown command: ${command}`);
131
- printHelp();
132
- process.exit(1);
133
139
  }
140
+
141
+ run().catch((error) => {
142
+ console.error(error);
143
+ process.exitCode = 1;
144
+ });
package/lib/deploy.js CHANGED
@@ -15,7 +15,7 @@ async function deploy(config, options = {}) {
15
15
  // 1. Build the site
16
16
  console.log("Building site...");
17
17
  config.includeDrafts = false;
18
- build(config);
18
+ await build(config);
19
19
  console.log("Build complete.\n");
20
20
 
21
21
  // 2. Check git is initialized
package/lib/index.js CHANGED
@@ -6,8 +6,13 @@ const HomePagination = require("./home");
6
6
  const AllPostsPage = require("./all_posts");
7
7
  const Posts = require("./posts");
8
8
  const TagPages = require("./tag");
9
+ const {
10
+ beginImageBuild,
11
+ finishImageBuild,
12
+ publishImage,
13
+ } = require("./mod/image");
9
14
 
10
- function copyDirectoryRecursive(src, dest) {
15
+ async function copyDirectoryRecursive(src, dest, config) {
11
16
  fs.mkdirSync(dest, { recursive: true });
12
17
  const entries = fs.readdirSync(src, { withFileTypes: true });
13
18
 
@@ -16,9 +21,11 @@ function copyDirectoryRecursive(src, dest) {
16
21
  const destPath = path.join(dest, entry.name);
17
22
 
18
23
  if (entry.isDirectory()) {
19
- copyDirectoryRecursive(srcPath, destPath);
24
+ await copyDirectoryRecursive(srcPath, destPath, config);
25
+ } else if (path.extname(entry.name).toLowerCase() === ".png") {
26
+ await publishImage(srcPath, destPath, config, undefined, false);
20
27
  } else {
21
- fs.copyFileSync(srcPath, destPath);
28
+ await fs.promises.copyFile(srcPath, destPath);
22
29
  }
23
30
  }
24
31
  }
@@ -35,9 +42,10 @@ function createPostTranslationIndex(languageConfigs) {
35
42
 
36
43
  const translations = [];
37
44
  for (const config of languageConfigs) {
38
- const sourceFile = config.language === config.defaultLanguage
39
- ? "index.md"
40
- : `index.${config.language}.md`;
45
+ const sourceFile =
46
+ config.language === config.defaultLanguage
47
+ ? "index.md"
48
+ : `index.${config.language}.md`;
41
49
  const sourcePath = path.join(postsDir, entry.name, sourceFile);
42
50
  if (!fs.existsSync(sourcePath)) continue;
43
51
 
@@ -65,18 +73,21 @@ function createAboutTranslations(languageConfigs) {
65
73
 
66
74
  const contentDir = languageConfigs[0].dev.content;
67
75
  return languageConfigs.flatMap((config) => {
68
- const sourceFile = config.language === config.defaultLanguage
69
- ? "about.md"
70
- : `about.${config.language}.md`;
76
+ const sourceFile =
77
+ config.language === config.defaultLanguage
78
+ ? "about.md"
79
+ : `about.${config.language}.md`;
71
80
  if (!fs.existsSync(path.join(contentDir, sourceFile))) return [];
72
81
 
73
- return [{
74
- language: config.language,
75
- languageName: config.languageName,
76
- path: `${config.basePath || "/"}about/`,
77
- url: `${config.blogsite}/about/`,
78
- isDefault: config.language === config.defaultLanguage,
79
- }];
82
+ return [
83
+ {
84
+ language: config.language,
85
+ languageName: config.languageName,
86
+ path: `${config.basePath || "/"}about/`,
87
+ url: `${config.blogsite}/about/`,
88
+ isDefault: config.language === config.defaultLanguage,
89
+ },
90
+ ];
80
91
  });
81
92
  }
82
93
 
@@ -110,16 +121,16 @@ function createTagListTranslations(languageConfigs) {
110
121
  }));
111
122
  }
112
123
 
113
- function buildLanguage(config) {
124
+ async function buildLanguage(config) {
114
125
  fs.mkdirSync(config.dev.outdir, { recursive: true });
115
126
 
116
127
  // Create post pages in output directory
117
128
  const posts = new Posts(config);
118
129
  const postObjects = posts.createPostObjects();
119
130
 
120
- postObjects.forEach((post) => {
121
- post.generateContent("post.html");
122
- });
131
+ for (const post of postObjects) {
132
+ await post.generateContent("post.html");
133
+ }
123
134
 
124
135
  // Create home page and pagination in output/page directory
125
136
  const homePagination = new HomePagination(config);
@@ -137,31 +148,38 @@ function buildLanguage(config) {
137
148
  tagPages.generateContent(postObjects);
138
149
 
139
150
  // Create about page in output/about directory
140
- const aboutFile = config.language === config.defaultLanguage
141
- ? "about.md"
142
- : `about.${config.language}.md`;
151
+ const aboutFile =
152
+ config.language === config.defaultLanguage
153
+ ? "about.md"
154
+ : `about.${config.language}.md`;
143
155
  const aboutPath = path.resolve(config.dev.content, aboutFile);
144
156
  if (fs.existsSync(aboutPath)) {
145
157
  const aboutPage = new Page(config);
146
158
  aboutPage.readSource(aboutPath, "about");
147
159
  aboutPage.translations = config.aboutTranslations || [];
148
- aboutPage.generateContent("page.html", "about");
160
+ await aboutPage.generateContent("page.html", "about");
149
161
  }
150
162
 
151
163
  // Copy static directory to output directory
152
164
  const staticImagesDir = path.join(config.dev.staticDir, "images");
153
165
  if (fs.existsSync(staticImagesDir)) {
154
- copyDirectoryRecursive(staticImagesDir, path.join(config.dev.outdir, "images"));
166
+ await copyDirectoryRecursive(
167
+ staticImagesDir,
168
+ path.join(config.dev.outdir, "images"),
169
+ config,
170
+ );
155
171
  }
156
172
 
157
173
  // Copy theme assets to output directory
158
174
  const themeAssetsDir = path.join(config.themePath, "assets");
159
175
  if (fs.existsSync(themeAssetsDir)) {
160
- copyDirectoryRecursive(themeAssetsDir, config.dev.outdir);
176
+ await copyDirectoryRecursive(themeAssetsDir, config.dev.outdir, config);
161
177
  }
162
178
  }
163
179
 
164
- function build(config) {
180
+ async function build(config) {
181
+ beginImageBuild();
182
+
165
183
  // Remove the output directory
166
184
  if (fs.existsSync(config.dev.outdir))
167
185
  fs.rmSync(config.dev.outdir, { recursive: true });
@@ -183,7 +201,10 @@ function build(config) {
183
201
  languageConfig.allPostsTranslations = allPostsTranslations;
184
202
  languageConfig.tagListTranslations = tagListTranslations;
185
203
  });
186
- languageConfigs.forEach(buildLanguage);
204
+ for (const languageConfig of languageConfigs) {
205
+ await buildLanguage(languageConfig);
206
+ }
207
+ await finishImageBuild(config);
187
208
 
188
209
  // Create CNAME file for GitHub Pages
189
210
  if (config.githubCNAME)
package/lib/init.js CHANGED
@@ -2,6 +2,12 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { execSync } = require("child_process");
4
4
  const readline = require("readline");
5
+ const { CACHE_SCHEMA_VERSION } = require("./mod/image");
6
+
7
+ const IMAGE_CACHE_SCHEMA = `v${CACHE_SCHEMA_VERSION}`;
8
+ const IMAGE_CACHE_IGNORE_RULE = ".fossbook-cache/";
9
+ const IMAGE_CACHE_SAVE_CONDITION =
10
+ "steps.image-cache-restore.outputs.cache-hit != 'true' && steps.image-cache-status.outputs.new-entries == 'true'";
5
11
 
6
12
  const DEPLOY_WORKFLOW = `name: Deploy to GitHub Pages
7
13
 
@@ -36,9 +42,41 @@ jobs:
36
42
  - name: Install dependencies
37
43
  run: npm ci
38
44
 
45
+ - name: Resolve Fossbook image cache
46
+ id: image-cache-config
47
+ run: node -e "require('fossbook/lib/mod/github_cache').writeGitHubCacheOutputs()"
48
+
49
+ - name: Restore Fossbook image cache
50
+ id: image-cache-restore
51
+ uses: actions/cache/restore@v4
52
+ continue-on-error: true
53
+ with:
54
+ path: \${{ steps.image-cache-config.outputs.cache-path }}/images
55
+ key: \${{ runner.os }}-\${{ runner.arch }}-fossbook-images-${IMAGE_CACHE_SCHEMA}-\${{ steps.image-cache-config.outputs.input-hash }}-\${{ github.run_id }}-\${{ github.run_attempt }}
56
+ restore-keys: |
57
+ \${{ runner.os }}-\${{ runner.arch }}-fossbook-images-${IMAGE_CACHE_SCHEMA}-\${{ steps.image-cache-config.outputs.input-hash }}-
58
+ \${{ runner.os }}-\${{ runner.arch }}-fossbook-images-${IMAGE_CACHE_SCHEMA}-
59
+
39
60
  - name: Build site
40
61
  run: npx fossbook build
41
62
 
63
+ - name: Read Fossbook image cache status
64
+ id: image-cache-status
65
+ continue-on-error: true
66
+ shell: bash
67
+ env:
68
+ FOSSBOOK_CACHE_STATUS: \${{ steps.image-cache-config.outputs.status-path }}
69
+ run: |
70
+ node -e "const fs=require('fs');let writes=0;try{writes=JSON.parse(fs.readFileSync(process.env.FOSSBOOK_CACHE_STATUS,'utf8')).writes||0}catch(error){console.warn(error.message)}fs.appendFileSync(process.env.GITHUB_OUTPUT, 'new-entries='+(writes>0)+'\\n')"
71
+
72
+ - name: Save Fossbook image cache
73
+ if: \${{ ${IMAGE_CACHE_SAVE_CONDITION} }}
74
+ uses: actions/cache/save@v4
75
+ continue-on-error: true
76
+ with:
77
+ path: \${{ steps.image-cache-config.outputs.cache-path }}/images
78
+ key: \${{ runner.os }}-\${{ runner.arch }}-fossbook-images-${IMAGE_CACHE_SCHEMA}-\${{ steps.image-cache-config.outputs.input-hash }}-\${{ github.run_id }}-\${{ github.run_attempt }}
79
+
42
80
  - name: Setup Pages
43
81
  uses: actions/configure-pages@v4
44
82
  with:
@@ -70,10 +108,7 @@ function initProject(options = {}) {
70
108
  }
71
109
 
72
110
  // Create directories
73
- const dirs = [
74
- "content/posts",
75
- "static/images",
76
- ];
111
+ const dirs = ["content/posts", "static/images"];
77
112
 
78
113
  dirs.forEach((dir) => {
79
114
  const dirPath = path.join(cwd, dir);
@@ -95,8 +130,13 @@ function initProject(options = {}) {
95
130
  // Create .gitignore
96
131
  const gitignorePath = path.join(cwd, ".gitignore");
97
132
  if (!fs.existsSync(gitignorePath)) {
98
- fs.writeFileSync(gitignorePath, "node_modules/\npublic/\n");
133
+ fs.writeFileSync(
134
+ gitignorePath,
135
+ `node_modules/\npublic/\n${IMAGE_CACHE_IGNORE_RULE}\n`,
136
+ );
99
137
  console.log("Created: .gitignore");
138
+ } else if (ensureGitignoreRule(gitignorePath, IMAGE_CACHE_IGNORE_RULE)) {
139
+ console.log(`Updated: .gitignore (${IMAGE_CACHE_IGNORE_RULE})`);
100
140
  }
101
141
 
102
142
  // Create fossbook.config.js
@@ -119,6 +159,14 @@ function initProject(options = {}) {
119
159
  image: "",
120
160
  theme: "archie",
121
161
 
162
+ // PNG publishing optimization
163
+ imageOptimization: true,
164
+ imageMaxWidth: 1400,
165
+ imageMaxWidthOverride: 1200,
166
+ imageWebP: true,
167
+ imageResponsiveWidths: [600, 800, 1000],
168
+ cacheDir: "./.fossbook-cache",
169
+
122
170
  // Languages (optional)
123
171
  defaultLanguage: "en",
124
172
  defaultLanguageInSubdir: false,
@@ -192,7 +240,9 @@ Welcome to my blog!
192
240
  execSync("npm install", { cwd, stdio: "inherit" });
193
241
  console.log("Created: package-lock.json");
194
242
  } catch {
195
- console.warn("Warning: npm install failed. You may need to run it manually.");
243
+ console.warn(
244
+ "Warning: npm install failed. You may need to run it manually.",
245
+ );
196
246
  }
197
247
  }
198
248
 
@@ -203,13 +253,13 @@ Welcome to my blog!
203
253
 
204
254
  console.log("\nSite initialized! Next steps:");
205
255
  if (!options.github) {
206
- console.log(' 1. Edit fossbook.config.js with your site settings');
256
+ console.log(" 1. Edit fossbook.config.js with your site settings");
207
257
  console.log(' 2. Run: fossbook new "My First Post"');
208
258
  console.log(" 3. Edit your post in content/posts/");
209
259
  console.log(" 4. Run: fossbook build");
210
260
  console.log(" 5. Run: fossbook serve");
211
261
  } else {
212
- console.log(' 1. Edit fossbook.config.js with your site settings');
262
+ console.log(" 1. Edit fossbook.config.js with your site settings");
213
263
  console.log(' 2. Run: fossbook new "My First Post"');
214
264
  console.log(" 3. Edit your post in content/posts/");
215
265
  console.log(" 4. Run: fossbook deploy");
@@ -223,10 +273,10 @@ function initGitHub(cwd) {
223
273
  } catch {
224
274
  console.error(
225
275
  "Error: GitHub CLI (gh) is not installed.\n" +
226
- "Install it: https://cli.github.com/\n" +
227
- " Linux: sudo apt install gh\n" +
228
- " macOS: brew install gh\n" +
229
- " Windows: winget install GitHub.cli"
276
+ "Install it: https://cli.github.com/\n" +
277
+ " Linux: sudo apt install gh\n" +
278
+ " macOS: brew install gh\n" +
279
+ " Windows: winget install GitHub.cli",
230
280
  );
231
281
  process.exit(1);
232
282
  }
@@ -256,7 +306,9 @@ function initGitHub(cwd) {
256
306
  });
257
307
  } catch {
258
308
  // Repo may already exist — ask user what to do
259
- console.warn(`\nA GitHub repository named "${repoName}" already exists on your account.`);
309
+ console.warn(
310
+ `\nA GitHub repository named "${repoName}" already exists on your account.`,
311
+ );
260
312
  const answer = promptSync("Push to the existing repo? (y/n): ");
261
313
  if (answer.toLowerCase() !== "y") {
262
314
  console.log("Cancelled. Your local site files are still available.");
@@ -275,11 +327,18 @@ function initGitHub(cwd) {
275
327
  stdio: ["pipe", "pipe", "ignore"],
276
328
  }).trim();
277
329
  const remoteUrl = `git@github.com:${ghUser}/${repoName}.git`;
278
- execSync(`git remote add origin ${remoteUrl}`, { cwd, stdio: "inherit" });
330
+ execSync(`git remote add origin ${remoteUrl}`, {
331
+ cwd,
332
+ stdio: "inherit",
333
+ });
279
334
  console.log(`Added remote: ${remoteUrl}`);
280
335
  } catch {
281
- console.warn("Warning: Could not add git remote. Add it manually with:");
282
- console.warn(` git remote add origin git@github.com:<user>/${repoName}.git`);
336
+ console.warn(
337
+ "Warning: Could not add git remote. Add it manually with:",
338
+ );
339
+ console.warn(
340
+ ` git remote add origin git@github.com:<user>/${repoName}.git`,
341
+ );
283
342
  }
284
343
  }
285
344
  }
@@ -288,14 +347,19 @@ function initGitHub(cwd) {
288
347
  console.log("Committing and pushing to GitHub...");
289
348
  execSync("git add -A", { cwd, stdio: "inherit" });
290
349
  try {
291
- execSync('git commit -m "Initial fossbook site"', { cwd, stdio: "inherit" });
350
+ execSync('git commit -m "Initial fossbook site"', {
351
+ cwd,
352
+ stdio: "inherit",
353
+ });
292
354
  } catch {
293
355
  // Nothing to commit
294
356
  }
295
357
  try {
296
358
  execSync("git push -u origin main", { cwd, stdio: "inherit" });
297
359
  } catch (e) {
298
- console.warn("Warning: Could not push to origin. You may need to push manually.");
360
+ console.warn(
361
+ "Warning: Could not push to origin. You may need to push manually.",
362
+ );
299
363
  }
300
364
 
301
365
  console.log("\nGitHub repository created and pushed!");
@@ -323,4 +387,24 @@ function promptSync(question) {
323
387
  return str.trim();
324
388
  }
325
389
 
326
- module.exports = { initProject };
390
+ function shouldSaveImageCache(cacheHit, newEntries) {
391
+ return cacheHit !== "true" && newEntries === true;
392
+ }
393
+
394
+ function ensureGitignoreRule(gitignorePath, rule) {
395
+ const contents = fs.readFileSync(gitignorePath, "utf8");
396
+ const rules = contents.split(/\r?\n/).map((line) => line.trim());
397
+ if (rules.includes(rule)) return false;
398
+
399
+ const separator = contents.length > 0 && !contents.endsWith("\n") ? "\n" : "";
400
+ fs.appendFileSync(gitignorePath, `${separator}${rule}\n`);
401
+ return true;
402
+ }
403
+
404
+ module.exports = {
405
+ DEPLOY_WORKFLOW,
406
+ IMAGE_CACHE_SCHEMA,
407
+ ensureGitignoreRule,
408
+ initProject,
409
+ shouldSaveImageCache,
410
+ };
package/lib/mod/config.js CHANGED
@@ -19,9 +19,15 @@ const defaults = {
19
19
  defaultLanguage: "en",
20
20
  defaultLanguageInSubdir: false,
21
21
  languages: null,
22
- basePath: "", // auto-detected for GitHub Pages project sites, e.g. "/repo-name/"
23
- postsPath: "posts", // URL prefix for posts: "posts" serves articles at /posts/<slug>/; set "" for root
24
- comments: null, // { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" }
22
+ basePath: "", // auto-detected for GitHub Pages project sites, e.g. "/repo-name/"
23
+ postsPath: "posts", // URL prefix for posts: "posts" serves articles at /posts/<slug>/; set "" for root
24
+ comments: null, // { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" }
25
+ imageOptimization: true,
26
+ imageMaxWidth: 1400,
27
+ imageMaxWidthOverride: 1200,
28
+ imageWebP: true,
29
+ imageResponsiveWidths: [600, 800, 1000],
30
+ cacheDir: "./.fossbook-cache",
25
31
  content: "./content",
26
32
  postsDir: "./content/posts",
27
33
  outputDir: "./public",
@@ -44,7 +50,9 @@ function resolveThemePath(config) {
44
50
  if (fs.existsSync(builtinThemePath)) {
45
51
  return builtinThemePath;
46
52
  }
47
- console.error(`Theme "${themeName}" not found in ${userThemePath} or ${builtinThemePath}`);
53
+ console.error(
54
+ `Theme "${themeName}" not found in ${userThemePath} or ${builtinThemePath}`,
55
+ );
48
56
  process.exit(1);
49
57
  }
50
58
 
@@ -76,6 +84,7 @@ function loadConfig(configPath) {
76
84
  content: path.resolve(merged.content),
77
85
  about: path.resolve(merged.content, "about.md"),
78
86
  outdir: path.resolve(merged.outputDir),
87
+ cacheDir: path.resolve(merged.cacheDir),
79
88
  themePath: path.dirname(themePath), // e.g. /path/to/themes
80
89
  staticDir: path.resolve(merged.staticDir),
81
90
  };
@@ -105,23 +114,36 @@ function loadConfig(configPath) {
105
114
 
106
115
  function createLanguageConfigs(config, configPath) {
107
116
  const configuredLanguages = config.languages;
108
- const hasMultipleLanguages = configuredLanguages !== null && configuredLanguages !== undefined;
117
+ const hasMultipleLanguages =
118
+ configuredLanguages !== null && configuredLanguages !== undefined;
109
119
  const languages = hasMultipleLanguages
110
120
  ? configuredLanguages
111
121
  : { [config.defaultLanguage]: {} };
112
122
 
113
- if (typeof languages !== "object" || Array.isArray(languages) || Object.keys(languages).length === 0) {
123
+ if (
124
+ typeof languages !== "object" ||
125
+ Array.isArray(languages) ||
126
+ Object.keys(languages).length === 0
127
+ ) {
114
128
  throw new Error("languages must be a non-empty object");
115
129
  }
116
- if (!Object.prototype.hasOwnProperty.call(languages, config.defaultLanguage)) {
117
- throw new Error(`defaultLanguage "${config.defaultLanguage}" is not defined in languages`);
130
+ if (
131
+ !Object.prototype.hasOwnProperty.call(languages, config.defaultLanguage)
132
+ ) {
133
+ throw new Error(
134
+ `defaultLanguage "${config.defaultLanguage}" is not defined in languages`,
135
+ );
118
136
  }
119
137
 
120
138
  return Object.entries(languages).map(([language, overrides]) => {
121
139
  if (!/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/.test(language)) {
122
140
  throw new Error(`Invalid language key "${language}"`);
123
141
  }
124
- if (!overrides || typeof overrides !== "object" || Array.isArray(overrides)) {
142
+ if (
143
+ !overrides ||
144
+ typeof overrides !== "object" ||
145
+ Array.isArray(overrides)
146
+ ) {
125
147
  throw new Error(`languages.${language} must be an object`);
126
148
  }
127
149
 
@@ -129,7 +151,8 @@ function createLanguageConfigs(config, configPath) {
129
151
  ? loadLanguageConfig(configPath, language)
130
152
  : {};
131
153
 
132
- const useSubdirectory = config.defaultLanguageInSubdir || language !== config.defaultLanguage;
154
+ const useSubdirectory =
155
+ config.defaultLanguageInSubdir || language !== config.defaultLanguage;
133
156
  const languagePrefix = useSubdirectory ? language : "";
134
157
  const basePath = appendPathSegment(config.basePath, languagePrefix);
135
158
  const blogsite = appendUrlSegment(config.blogsite, languagePrefix);
@@ -141,7 +164,8 @@ function createLanguageConfigs(config, configPath) {
141
164
  defaultLanguage: config.defaultLanguage,
142
165
  defaultLanguageInSubdir: config.defaultLanguageInSubdir,
143
166
  language,
144
- languageName: fileOverrides.languageName || overrides.languageName || language,
167
+ languageName:
168
+ fileOverrides.languageName || overrides.languageName || language,
145
169
  locale: fileOverrides.locale || overrides.locale || language,
146
170
  languagePrefix,
147
171
  basePath,
@@ -166,8 +190,14 @@ function loadLanguageConfig(configPath, language) {
166
190
  if (!fs.existsSync(languageConfigPath)) return {};
167
191
 
168
192
  const languageConfig = require(languageConfigPath);
169
- if (!languageConfig || typeof languageConfig !== "object" || Array.isArray(languageConfig)) {
170
- throw new Error(`${path.basename(languageConfigPath)} must export an object`);
193
+ if (
194
+ !languageConfig ||
195
+ typeof languageConfig !== "object" ||
196
+ Array.isArray(languageConfig)
197
+ ) {
198
+ throw new Error(
199
+ `${path.basename(languageConfigPath)} must export an object`,
200
+ );
171
201
  }
172
202
  return languageConfig;
173
203
  }