vending-mocha 0.1.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.
Files changed (42) hide show
  1. package/.github/workflows/npm-publish.yml +45 -0
  2. package/.github/workflows/regenerate-dist.yml +38 -0
  3. package/LICENSE +201 -0
  4. package/README.md +85 -0
  5. package/SKILL.md +82 -0
  6. package/bin/cli.js +441 -0
  7. package/eslint.config.js +23 -0
  8. package/index.html +16 -0
  9. package/package.json +57 -0
  10. package/posts/customization-guide.md +45 -0
  11. package/posts/deploy-to-github-pages.md +109 -0
  12. package/posts/hello-world.md +20 -0
  13. package/posts/markdown-features.md +57 -0
  14. package/prerender.js +221 -0
  15. package/projects/legacy-api.md +7 -0
  16. package/projects/task-master.md +7 -0
  17. package/projects/vending-mocha.md +7 -0
  18. package/scripts/generate-posts-data.js +41 -0
  19. package/scripts/generate-projects-data.js +40 -0
  20. package/scripts/generate-rss.js +75 -0
  21. package/src/App.css +566 -0
  22. package/src/App.tsx +33 -0
  23. package/src/components/Footer.tsx +11 -0
  24. package/src/components/MarkdownImage.tsx +40 -0
  25. package/src/components/Profile.tsx +45 -0
  26. package/src/components/SiteHeader.tsx +44 -0
  27. package/src/context/ThemeContext.tsx +75 -0
  28. package/src/entry-client.tsx +32 -0
  29. package/src/entry-server.tsx +26 -0
  30. package/src/pages/BlogPost.tsx +93 -0
  31. package/src/pages/HomePage.tsx +85 -0
  32. package/src/pages/Projects.tsx +50 -0
  33. package/src/site.config.ts +38 -0
  34. package/src/utils/basePath.ts +21 -0
  35. package/src/utils/date.ts +17 -0
  36. package/src/utils/frontmatter.ts +32 -0
  37. package/static/favicon.ico +0 -0
  38. package/static/images/profile.png +0 -0
  39. package/tsconfig.app.json +36 -0
  40. package/tsconfig.json +7 -0
  41. package/tsconfig.node.json +26 -0
  42. package/vite.config.ts +61 -0
package/bin/cli.js ADDED
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { execSync } from 'child_process';
6
+ import { fileURLToPath } from 'url';
7
+ import inquirer from 'inquirer';
8
+ import chalk from 'chalk';
9
+ import figlet from 'figlet';
10
+ import { Command } from 'commander';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ const IGNORE_FILES = [
16
+ 'node_modules',
17
+ '.git',
18
+ '.github',
19
+ 'dist',
20
+ 'docs',
21
+ 'bin',
22
+ 'package-lock.json',
23
+ '.DS_Store'
24
+ ];
25
+
26
+ function getAsciiArt() {
27
+ const cup = `
28
+ ( (
29
+ ) )
30
+ .______.
31
+ | |]
32
+ \\ /
33
+ \`----'
34
+ `;
35
+
36
+ // 1. Generate Plain Text
37
+ const vending = figlet.textSync('Vending', { horizontalLayout: 'full' });
38
+ const mocha = figlet.textSync('Mocha', { horizontalLayout: 'full' });
39
+
40
+ // 2. Prepare Lines
41
+ const vendingLines = vending.split('\n');
42
+ const mochaLines = mocha.split('\n');
43
+ const cupLines = cup.split('\n').filter(line => line.trim().length > 0);
44
+
45
+ // 3. Calculate Layout Dimensions
46
+ const mochaWidth = Math.max(...mochaLines.map(line => line.length));
47
+
48
+ // Combine Mocha + Cup logic to find total max width
49
+ // We need to see how wide the bottom section (Mocha + Cup) is vs the top section (Vending)
50
+
51
+ // Bottom Section Width
52
+ let maxBottomWidth = 0;
53
+ const bottomHeight = Math.max(mochaLines.length, cupLines.length);
54
+ for (let i = 0; i < bottomHeight; i++) {
55
+ const mLen = (mochaLines[i] || '').length;
56
+ const cLen = (cupLines[i] || '').length;
57
+ // Mocha + 3 spaces + Cup
58
+ const lineLen = Math.max(mLen, mochaWidth) + 3 + cLen;
59
+ if (lineLen > maxBottomWidth) maxBottomWidth = lineLen;
60
+ }
61
+
62
+ // Top Section Width
63
+ const maxTopWidth = Math.max(...vendingLines.map(line => line.length));
64
+
65
+ // Overall Max Width
66
+ const contentWidth = Math.max(maxTopWidth, maxBottomWidth);
67
+
68
+ // 4. Construct Art with Border
69
+ const lines = [];
70
+
71
+ // Top Border
72
+ // Width = contentWidth + 2 spaces padding on each side = contentWidth + 4 ?
73
+ // Let's stick to the design: │ Content │ (2 spaces padding)
74
+ // So inner width = contentWidth + 4.
75
+ // Border line length = contentWidth + 4.
76
+
77
+ const borderLine = '─'.repeat(contentWidth + 4);
78
+ lines.push(chalk.cyan('╭' + borderLine + '╮'));
79
+
80
+ // Helper to push a bordered line
81
+ const pushLine = (str, strLength) => {
82
+ const padding = ' '.repeat(contentWidth - strLength);
83
+ // We manually construct the line: │ <str> <padding> │
84
+ // But <str> might contain ANSI codes, so we need to be careful not to count them in length,
85
+ // which is why we pass strLength explicitly.
86
+ lines.push(chalk.cyan('│ ') + str + padding + chalk.cyan(' │'));
87
+ };
88
+
89
+ // Render Vending (Top)
90
+ for (const line of vendingLines) {
91
+ pushLine(chalk.cyan(line), line.length);
92
+ }
93
+
94
+ // Gap? The user art in previous turn didn't have a gap, but it looks better with one maybe?
95
+ // The previous output didn't have a huge gap. Let's not add extra vertical gap to keep it compact unless needed.
96
+ // Actually, let's add one empty line for separation if it looks cramped.
97
+ // Figlet art usually has some whitespace. Let's mimic the test_border.js which didn't verify vertical spacing explicitly but looked okay.
98
+ // I'll skip explicit vertical gap to match previous compactness, unless I see reason to add it.
99
+
100
+ // Render Mocha + Cup (Bottom)
101
+ for (let i = 0; i < bottomHeight; i++) {
102
+ const mLine = mochaLines[i] || '';
103
+ const cLine = cupLines[i] || '';
104
+
105
+ // Pad mocha part to mochaWidth
106
+ const mPad = ' '.repeat(mochaWidth - mLine.length);
107
+
108
+ const combinedStr = chalk.cyan(mLine) + mPad + ' ' + chalk.yellow(cLine);
109
+ const combinedLen = mochaWidth + 3 + cLine.length;
110
+
111
+ pushLine(combinedStr, combinedLen);
112
+ }
113
+
114
+ // Bottom Border
115
+ lines.push(chalk.cyan('╰' + borderLine + '╯'));
116
+ lines.push(''); // Final newline
117
+
118
+ return lines.join('\n');
119
+ }
120
+
121
+ function copyRecursiveSync(src, dest, destRoot) {
122
+ const exists = fs.existsSync(src);
123
+ const stats = exists && fs.statSync(src);
124
+ const isDirectory = exists && stats.isDirectory();
125
+ const basename = path.basename(src);
126
+
127
+ if (IGNORE_FILES.includes(basename)) {
128
+ return;
129
+ }
130
+
131
+ // Prevent infinite recursion if destination is inside source
132
+ if (destRoot && path.resolve(src) === path.resolve(destRoot)) {
133
+ return;
134
+ }
135
+
136
+ if (isDirectory) {
137
+ if (!fs.existsSync(dest)) {
138
+ fs.mkdirSync(dest);
139
+ }
140
+
141
+ fs.readdirSync(src).forEach((childItemName) => {
142
+ copyRecursiveSync(
143
+ path.join(src, childItemName),
144
+ path.join(dest, childItemName),
145
+ destRoot
146
+ );
147
+ });
148
+ } else {
149
+ fs.copyFileSync(src, dest);
150
+ }
151
+ }
152
+
153
+ function updateSiteConfig(projectDir, config) {
154
+ const configPath = path.join(projectDir, 'src', 'site.config.ts');
155
+ if (fs.existsSync(configPath)) {
156
+ let content = fs.readFileSync(configPath, 'utf8');
157
+
158
+ // Update title
159
+ if (config.title) {
160
+ content = content.replace(/title:\s*".*?"/, `title: "${config.title}"`);
161
+ }
162
+
163
+ // Update URL
164
+ if (config.url) {
165
+ content = content.replace(/url:\s*".*?"/, `url: "${config.url}"`);
166
+ }
167
+
168
+ fs.writeFileSync(configPath, content, 'utf8');
169
+ }
170
+ }
171
+
172
+ function updatePackageJson(projectDir, config) {
173
+ const pkgPath = path.join(projectDir, 'package.json');
174
+ if (fs.existsSync(pkgPath)) {
175
+ try {
176
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
177
+ pkg.name = config.projectName;
178
+ pkg.version = '0.0.0';
179
+ pkg.description = config.description || `My new blog built with vending-mocha`;
180
+
181
+ // Remove the bin entry so the new project doesn't try to be a CLI itself
182
+ if (pkg.bin) {
183
+ delete pkg.bin;
184
+ }
185
+
186
+ // Remove CLI-specific dependencies from the new project
187
+ if (pkg.dependencies) {
188
+ delete pkg.dependencies['figlet'];
189
+ delete pkg.dependencies['inquirer'];
190
+ delete pkg.dependencies['chalk'];
191
+ delete pkg.dependencies['commander'];
192
+ }
193
+
194
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
195
+ } catch (e) {
196
+ console.warn(chalk.yellow('Failed to update package.json:', e.message));
197
+ }
198
+ }
199
+ }
200
+
201
+ async function handleNew(projectName, options) {
202
+ console.clear();
203
+ console.log(getAsciiArt());
204
+
205
+ // Interactive Prompts
206
+ const questions = [];
207
+
208
+ if (!projectName) {
209
+ questions.push({
210
+ type: 'input',
211
+ name: 'projectName',
212
+ message: 'What is the name of your new project?',
213
+ default: 'my-vending-mocha-blog',
214
+ validate: (input) => {
215
+ if (/^([a-z0-9\-\_\.]+)$/.test(input)) return true;
216
+ return 'Project name may only include letters, numbers, underscores and hashes.';
217
+ }
218
+ });
219
+ }
220
+
221
+ questions.push({
222
+ type: 'input',
223
+ name: 'title',
224
+ message: 'What is the title of your blog?',
225
+ default: (answers) => answers.projectName || projectName
226
+ });
227
+
228
+ questions.push({
229
+ type: 'input',
230
+ name: 'url',
231
+ message: 'What is the production URL of your blog?',
232
+ default: (answers) => `https://example.com/${answers.projectName || projectName}`
233
+ });
234
+
235
+ questions.push({
236
+ type: 'input',
237
+ name: 'description',
238
+ message: 'Write a short description for your blog:',
239
+ default: 'A personal blogging framework for developers.'
240
+ });
241
+
242
+ const answers = await inquirer.prompt(questions);
243
+
244
+ // Merge args and answers
245
+ const config = {
246
+ projectName: projectName || answers.projectName,
247
+ title: answers.title,
248
+ url: answers.url,
249
+ description: answers.description
250
+ };
251
+
252
+ const currentDir = process.cwd();
253
+ const projectDir = path.join(currentDir, config.projectName);
254
+ const templateDir = path.join(__dirname, '..');
255
+
256
+ if (fs.existsSync(projectDir)) {
257
+ console.error(chalk.red(`Directory ${config.projectName} already exists.`));
258
+ process.exit(1);
259
+ }
260
+
261
+ console.log(chalk.blue(`\nCreating new project in ${projectDir}...\n`));
262
+
263
+ try {
264
+ // 1. Copy files
265
+ const spinner = { start: () => console.log(chalk.gray('Copying template files...')), succeed: () => console.log(chalk.green('✔ Files copied')) }; // Simple mock spinner
266
+ spinner.start();
267
+ copyRecursiveSync(templateDir, projectDir, projectDir);
268
+ spinner.succeed();
269
+
270
+ // 2. Update config
271
+ console.log(chalk.gray('Updating configuration...'));
272
+ updateSiteConfig(projectDir, config);
273
+ console.log(chalk.green('✔ Configuration updated'));
274
+
275
+ // 3. Update package.json
276
+ console.log(chalk.gray('Updating package.json...'));
277
+ updatePackageJson(projectDir, config);
278
+ console.log(chalk.green('✔ package.json updated'));
279
+
280
+ // 4. Initialize Git
281
+ console.log(chalk.gray('Initializing git repository...'));
282
+ try {
283
+ execSync('git init', { cwd: projectDir, stdio: 'ignore' });
284
+ execSync('git add .', { cwd: projectDir, stdio: 'ignore' });
285
+ execSync('git commit -m "Initial commit from vending-mocha"', { cwd: projectDir, stdio: 'ignore' });
286
+ console.log(chalk.green('✔ Git initialized'));
287
+ } catch (e) {
288
+ console.warn(chalk.yellow('⚠ Failed to initialize git repository (git might not be installed).'));
289
+ }
290
+
291
+ console.log(chalk.green(`\nSuccess! Created ${config.projectName} at ${projectDir}`));
292
+ console.log('\nInside that directory, you can run:');
293
+ console.log(chalk.cyan(` cd ${config.projectName}`));
294
+ console.log(chalk.cyan(' npm install'));
295
+ console.log(chalk.cyan(' npm run dev'));
296
+ console.log('\nHappy blogging!');
297
+
298
+ } catch (error) {
299
+ console.error(chalk.red('Failed to create project:', error));
300
+ process.exit(1);
301
+ }
302
+ }
303
+
304
+ async function handleUpgrade() {
305
+ console.clear();
306
+ console.log(getAsciiArt());
307
+
308
+ const currentDir = process.cwd();
309
+ const templateDir = path.join(__dirname, '..');
310
+
311
+ // 1. Verify it is a vending-mocha project
312
+ const siteConfigPath = path.join(currentDir, 'src', 'site.config.ts');
313
+ const localPkgPath = path.join(currentDir, 'package.json');
314
+
315
+ if (!fs.existsSync(siteConfigPath) || !fs.existsSync(localPkgPath)) {
316
+ console.error(chalk.red('Error: Current directory does not appear to be a vending-mocha project.'));
317
+ console.error(chalk.yellow('Ensure you are in the root of your project (containing package.json and src/site.config.ts).'));
318
+ process.exit(1);
319
+ }
320
+
321
+ console.log(chalk.yellow('WARNING: This will overwrite project files to the latest version of vending-mocha.'));
322
+ console.log(chalk.yellow('Your content (posts/, projects/) and configuration (src/site.config.ts) will be preserved.'));
323
+ console.log(chalk.yellow('Please ensure you have committed your changes before proceeding.'));
324
+
325
+ const { proceed } = await inquirer.prompt([
326
+ {
327
+ type: 'confirm',
328
+ name: 'proceed',
329
+ message: 'Are you sure you want to upgrade?',
330
+ default: false
331
+ }
332
+ ]);
333
+
334
+ if (!proceed) {
335
+ console.log(chalk.blue('Upgrade cancelled.'));
336
+ process.exit(0);
337
+ }
338
+
339
+ console.log(chalk.blue('\nUpgrading project...\n'));
340
+
341
+ try {
342
+ // 2. Copy files with exclusions
343
+ const upgradeIgnore = [
344
+ ...IGNORE_FILES,
345
+ 'posts',
346
+ 'projects',
347
+ 'src/site.config.ts', // Important: Preserve config
348
+ 'package.json' // We handle this separately
349
+ ];
350
+
351
+ function copyUpgradeSync(src, dest) {
352
+ const basename = path.basename(src);
353
+ const relPath = path.relative(templateDir, src);
354
+
355
+ if (upgradeIgnore.includes(basename) || upgradeIgnore.includes(relPath)) {
356
+ return;
357
+ }
358
+
359
+ // Also ignore if it is exactly the file we want to skip (normalized)
360
+ if (relPath === 'src/site.config.ts') return;
361
+
362
+ // Prevent infinite recursion: if src is the current directory (destination), skip it
363
+ if (path.resolve(src) === path.resolve(currentDir)) {
364
+ return;
365
+ }
366
+
367
+ const exists = fs.existsSync(src);
368
+ const stats = exists && fs.statSync(src);
369
+ const isDirectory = exists && stats.isDirectory();
370
+
371
+ if (isDirectory) {
372
+ if (!fs.existsSync(dest)) {
373
+ fs.mkdirSync(dest);
374
+ }
375
+ fs.readdirSync(src).forEach((child) => {
376
+ copyUpgradeSync(path.join(src, child), path.join(dest, child));
377
+ });
378
+ } else {
379
+ fs.copyFileSync(src, dest);
380
+ }
381
+ }
382
+
383
+ const spinner = { start: () => console.log(chalk.gray('Updating core files...')), succeed: () => console.log(chalk.green('✔ Core files updated')) };
384
+ spinner.start();
385
+ copyUpgradeSync(templateDir, currentDir);
386
+ spinner.succeed();
387
+
388
+ // 3. Merge package.json
389
+ console.log(chalk.gray('Merging package.json...'));
390
+ const localPkg = JSON.parse(fs.readFileSync(localPkgPath, 'utf8'));
391
+ const templatePkg = JSON.parse(fs.readFileSync(path.join(templateDir, 'package.json'), 'utf8'));
392
+
393
+ // Update scripts
394
+ localPkg.scripts = { ...localPkg.scripts, ...templatePkg.scripts };
395
+
396
+ // Update dependencies (add new ones, update versions)
397
+ localPkg.dependencies = { ...localPkg.dependencies, ...templatePkg.dependencies };
398
+ localPkg.devDependencies = { ...localPkg.devDependencies, ...templatePkg.devDependencies };
399
+
400
+ // Ensure we remove CLI deps if they somehow crept in or strictly enforce clean deps
401
+ if (localPkg.dependencies) {
402
+ delete localPkg.dependencies['figlet'];
403
+ delete localPkg.dependencies['inquirer'];
404
+ delete localPkg.dependencies['chalk'];
405
+ delete localPkg.dependencies['commander'];
406
+ }
407
+
408
+ fs.writeFileSync(localPkgPath, JSON.stringify(localPkg, null, 2));
409
+ console.log(chalk.green('✔ package.json merged'));
410
+
411
+ console.log(chalk.green('\nUpgrade complete!'));
412
+ console.log(chalk.cyan('You may need to run `npm install` to update dependencies.'));
413
+
414
+ } catch (error) {
415
+ console.error(chalk.red('Failed to upgrade project:', error));
416
+ process.exit(1);
417
+ }
418
+ }
419
+
420
+ const program = new Command();
421
+
422
+ program
423
+ .name('vending-mocha')
424
+ .description('A personal blogging framework for developers')
425
+ .version('0.0.0')
426
+ .addHelpText('before', getAsciiArt());
427
+
428
+ program.command('new')
429
+ .description('Create a new Vending Mocha project')
430
+ .argument('[project-name]', 'Name of the project directory')
431
+ .action(handleNew);
432
+
433
+ program.command('upgrade')
434
+ .description('Upgrade an existing Vending Mocha project')
435
+ .action(handleUpgrade);
436
+
437
+ program.parse(process.argv);
438
+
439
+ if (!process.argv.slice(2).length) {
440
+ program.outputHelp();
441
+ }
@@ -0,0 +1,23 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+ export default defineConfig([
9
+ globalIgnores(['dist', 'docs']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.recommended,
15
+ reactHooks.configs.flat.recommended,
16
+ reactRefresh.configs.vite,
17
+ ],
18
+ languageOptions: {
19
+ ecmaVersion: 2020,
20
+ globals: globals.browser,
21
+ },
22
+ },
23
+ ])
package/index.html ADDED
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <link rel="icon" href="/favicon.ico" type="image/x-icon" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <!--app-head-->
9
+ </head>
10
+
11
+ <body>
12
+ <div id="root"><!--app-html--></div>
13
+ <script type="module" src="/src/entry-client.tsx"></script>
14
+ </body>
15
+
16
+ </html>
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "vending-mocha",
3
+ "description": "A personal blogging framework for developers.",
4
+ "version": "0.1.0",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "bin": {
8
+ "vending-mocha": "./bin/cli.js"
9
+ },
10
+ "scripts": {
11
+ "dev": "npm run build:all && vite",
12
+ "build:client": "tsc -b && vite build --outDir docs",
13
+ "build:server": "tsc -b && vite build --ssr src/entry-server.tsx --outDir dist/server",
14
+ "prebuild": "node scripts/generate-posts-data.js && node scripts/generate-projects-data.js",
15
+ "build": "npm run prebuild && npm run build:client && npm run build:server && node prerender.js && node scripts/generate-rss.js",
16
+ "build:all": "npm run build && for d in projects/*; do if [ -d \"$d\" ]; then (cd \"$d\" && npm run build); fi; done",
17
+ "preview": "npm run build:all && vite preview --outDir docs"
18
+ },
19
+ "dependencies": {
20
+ "@types/react-syntax-highlighter": "15.5.13",
21
+ "chalk": "^5.6.2",
22
+ "commander": "^14.0.3",
23
+ "date-fns": "^4.1.0",
24
+ "figlet": "^1.10.0",
25
+ "gray-matter": "^4.0.3",
26
+ "inquirer": "^13.2.5",
27
+ "lucide-react": "^0.564.0",
28
+ "react": "^19.2.0",
29
+ "react-dom": "^19.2.0",
30
+ "react-helmet-async": "^2.0.5",
31
+ "react-markdown": "^10.1.0",
32
+ "react-router-dom": "^7.13.0",
33
+ "react-syntax-highlighter": "16.1.0",
34
+ "remark-breaks": "^4.0.0",
35
+ "remark-gfm": "^4.0.1"
36
+ },
37
+ "overrides": {
38
+ "react-helmet-async": {
39
+ "react": "$react"
40
+ }
41
+ },
42
+ "devDependencies": {
43
+ "@eslint/js": "^9.39.1",
44
+ "@types/node": "^24.10.1",
45
+ "@types/react": "^19.2.7",
46
+ "@types/react-dom": "^19.2.3",
47
+ "@vitejs/plugin-react": "^5.1.1",
48
+ "eslint": "^9.39.1",
49
+ "eslint-plugin-react-hooks": "^7.0.1",
50
+ "eslint-plugin-react-refresh": "^0.4.24",
51
+ "globals": "^16.5.0",
52
+ "prettier": "^3.8.1",
53
+ "typescript": "~5.9.3",
54
+ "typescript-eslint": "^8.48.0",
55
+ "vite": "^7.3.1"
56
+ }
57
+ }
@@ -0,0 +1,45 @@
1
+ ---
2
+ title: "How to Customize Vending Mocha 🎨"
3
+ date: "2026-02-17 03:00:00"
4
+ summary: "A quick guide on how to update the configuration, styling, and content of your new blog."
5
+ ---
6
+
7
+ ## Customization Guide
8
+
9
+ So you've cloned **Vending Mocha**. What's next? Here is a quick guide to making it your own.
10
+
11
+ ### 1. Update Configuration
12
+
13
+ Open `src/site.config.ts` and update the following:
14
+
15
+ - **title**: Your name or site title.
16
+ - **description**: A short bio or site description.
17
+ - **url**: Your website URL (used for SEO).
18
+ - **theme**: Customize colors for light and dark modes.
19
+
20
+ ```typescript
21
+ export const siteConfig = {
22
+ title: "My Awesome Blog",
23
+ // ...
24
+ }
25
+ ```
26
+
27
+ ### 2. Add Your Projects
28
+
29
+ Add new `.md` files with frontmatter to `/projects/` to add your projects.
30
+
31
+ ```markdown
32
+ ---
33
+ title: "My Project"
34
+ description: "What is it?"
35
+ link: "https://github.com/..."
36
+ status: "active" // active, dead, inactive
37
+ weight: 5 // higher weight projects are displayed first
38
+ ---
39
+ ```
40
+
41
+ ### 3. Write Posts
42
+
43
+ Just add new `.md` files to `/posts/`. The filename becomes the slug (e.g., `/posts/my-post.md` -> `/post/my-post`).
44
+
45
+ That's it! You're ready to deploy. 🚀
@@ -0,0 +1,109 @@
1
+ ---
2
+ title: "Deploying to GitHub Pages 🚀"
3
+ date: "2026-02-17 04:00:00"
4
+ summary: "A step-by-step guide to deploying your Vending Mocha blog to GitHub Pages using GitHub Actions."
5
+ ---
6
+
7
+ Deploying your Vending Mocha blog to GitHub Pages is straightforward. We'll use GitHub Actions to automatically build and deploy your site whenever you push changes to the `main` branch.
8
+
9
+ ## Prerequisites
10
+
11
+ 1. A GitHub account.
12
+ 2. A Vending Mocha project pushed to a GitHub repository.
13
+
14
+ ## Step 1: Configure Your Site
15
+
16
+ Open `src/site.config.ts` and ensure the `url` property matches your GitHub Pages URL.
17
+
18
+ If you are using a custom domain:
19
+ ```typescript
20
+ export const siteConfig = {
21
+ title: "My Blog",
22
+ url: "https://www.example.com", // Your custom domain
23
+ // ...
24
+ };
25
+ ```
26
+
27
+ If you are using the default GitHub Pages URL (`username.github.io/repo-name`):
28
+ ```typescript
29
+ export const siteConfig = {
30
+ title: "My Blog",
31
+ url: "https://username.github.io/repo-name", // REPLACE with your actual URL
32
+ // ...
33
+ };
34
+ ```
35
+
36
+ > **Important:** The `url` setting is critical for asset loading, especially if your site is hosted in a subdirectory (like `/repo-name/`).
37
+
38
+ ## Step 2: Create the GitHub Actions Workflow
39
+
40
+ Create a new file in your repository at `.github/workflows/deploy.yml` and paste the following content:
41
+
42
+ ```yaml
43
+ name: Deploy to GitHub Pages
44
+
45
+ on:
46
+ push:
47
+ branches:
48
+ - main
49
+ paths-ignore:
50
+ - 'docs/**' # Don't trigger if only the output folder changes
51
+
52
+ jobs:
53
+ build_and_deploy:
54
+ runs-on: ubuntu-latest
55
+ permissions:
56
+ contents: write
57
+
58
+ steps:
59
+ - name: Checkout repository
60
+ uses: actions/checkout@v4
61
+
62
+ - name: Setup Node.js
63
+ uses: actions/setup-node@v4
64
+ with:
65
+ node-version: '20'
66
+ cache: 'npm'
67
+
68
+ - name: Install dependencies
69
+ run: npm ci
70
+
71
+ - name: Build project
72
+ run: npm run build:all
73
+
74
+ - name: Deploy to GitHub Pages
75
+ uses: peaceiris/actions-gh-pages@v3
76
+ with:
77
+ github_token: ${{ secrets.GITHUB_TOKEN }}
78
+ publish_dir: ./docs
79
+ cname: your-domain.com # Optional - if you are using a custom domain
80
+ ```
81
+
82
+ This workflow does the following:
83
+ 1. Triggers on every push to `main`.
84
+ 2. Sets up Node.js.
85
+ 3. Installs dependencies.
86
+ 4. Builds your site (and any sub-projects).
87
+ 5. Deploys the `docs` folder (the build output) to the `gh-pages` branch.
88
+
89
+ ## Step 3: Configure GitHub Pages Settings
90
+
91
+ 1. Go to your repository on GitHub.
92
+ 2. Navigate to **Settings** > **Pages**.
93
+ 3. Under **Build and deployment**, select **Deploy from a branch**.
94
+ 4. Under **Branch**, select `gh-pages` and ensure the folder is `/ (root)`.
95
+ 5. Click **Save**.
96
+
97
+ ## Step 4: Push and Verify
98
+
99
+ Commit and push your changes to GitHub:
100
+
101
+ ```bash
102
+ git add .
103
+ git commit -m "Add deployment workflow"
104
+ git push origin main
105
+ ```
106
+
107
+ Go to the **Actions** tab in your repository to watch the build progress. Once it completes (green checkmark), your site should be live at the URL you configured!
108
+
109
+ Happy blogging! ☕