feat: add blog system with static site generator

- Add blog posts in Markdown (_posts/)
- Build script converts MD to HTML at container build time
- First posts: building with AI lessons, quick start guide
- AGENTS.md documents blog writing style (unixsheikh-inspired)
This commit is contained in:
lotherk
2026-03-27 14:33:05 +00:00
parent c37af7cdb1
commit b2b2e80f0a
8 changed files with 667 additions and 6 deletions

2
.gitignore vendored
View File

@@ -3,12 +3,14 @@ node_modules/
.pnp
.pnp.js
docs/node_modules/
www/node_modules/
# Build
dist/
build/
docs/dist/
docs/.astro/
www/blog/
# Data
data/

View File

@@ -1,11 +1,26 @@
FROM nginx:alpine
FROM node:20-alpine
COPY www/ /usr/share/nginx/html/
COPY docker-entrypoint.d/ /docker-entrypoint.d/
WORKDIR /app
# Install dependencies for blog build
COPY www/package.json ./www/package.json
RUN cd www && npm install --silent 2>/dev/null || true
# Copy www source files
COPY www/ /app/www/
# Build blog (processes _posts/*.md to blog/*.html)
RUN node www/build-blog.js
# Run envsubst on HTML files
RUN apk add --no-cache gettext && \
chmod +x /docker-entrypoint.d/*.sh
chmod +x /docker-entrypoint.d/*.sh 2>/dev/null || true
# Copy entrypoint
COPY docker-entrypoint.d/ /docker-entrypoint.d/
RUN chmod +x /docker-entrypoint.d/*.sh
# Copy to nginx document root
COPY --from=0 /app/www/ /usr/share/nginx/html/
ENTRYPOINT ["/docker-entrypoint.d/30-envsubst.sh", "--", "nginx", "-g", "daemon off;"]
CMD ["nginx", "-g", "daemon off;"]

120
www/AGENTS.md Normal file
View File

@@ -0,0 +1,120 @@
# DearDiary Website & Blog - Agent Instructions
## Overview
The DearDiary website is a static HTML site generated from Markdown blog posts. No frameworks, no build complexity.
## Structure
```
www/
├── _posts/ # Blog posts in Markdown (source)
│ └── YYYY-MM-DD-slug.md
├── blog/ # Generated HTML (don't edit manually)
│ ├── index.html
│ └── YYYY-MM-DD-slug/
│ └── index.html
├── css/
│ └── styles.css
├── docs/ # Documentation (also static HTML)
├── js/
│ └── main.js
├── index.html # Main website (uses envsubst for templating)
├── build-blog.js # Blog generator script
└── package.json # npm deps (gray-matter for parsing)
```
## Building
```bash
cd www
npm install
node build-blog.js
```
This converts `_posts/*.md``blog/*.html`.
## Blog Post Format
Frontmatter:
```yaml
---
title: Your Post Title
date: 2026-03-27
author: Konrad Lother
excerpt: One sentence summary for the blog index
---
```
## Tone & Style Guide
Read unixsheikh.com for inspiration. Key characteristics:
### Voice
- **Direct and authoritative** - Say what you mean, don't hedge unnecessarily
- **Personal opinion is welcome** - Write from a first-person perspective
- **Technical but accessible** - Explain complex topics simply
- **Not afraid to be contrarian** - Challenge conventional wisdom when you disagree with it
### Structure
- **Strong opening** - Hook the reader immediately
- **Clear sections** - Use headers to organize thoughts
- **Concrete examples** - Illustrate abstract concepts
- **Practical takeaways** - End with actionable advice
### What to Avoid
- Corporate/tech-blog speak ("leverage", "synergy", "cutting-edge")
- Unnecessary hedging ("might", "could potentially", "in some cases")
- Padding or fluff - every sentence should add value
- Being mean-spirited - critique ideas, not people
### Example Openers
Bad:
> "In today's fast-paced digital world, journaling has evolved..."
Good:
> "Traditional journaling apps ask you to write long entries at the end of the day. But let's be honest - who has time for that?"
### Length
- 500-1500 words is ideal
- If you're writing more, consider splitting into multiple posts
- Short posts can be 200-300 words if the idea is complete
## Topics to Cover
### DearDiary-Specific
- How to use features effectively
- Tips and tricks for better journaling
- Behind-the-scenes development stories
- Why AI-assisted journaling makes sense
- Privacy and self-hosting benefits
### General Tech Thoughts
- Software craftsmanship
- Human-AI collaboration
- Simplicity in development
- Critical thinking about tech trends
- The value of boring technology
### Personal/Philosophy
- Productivity and reflection
- Memory and identity
- The importance of记录 (record-keeping)
- Learning from daily life
## Post Ideas
1. Why capture events, not essays (the thesis behind DearDiary)
2. My journaling experiment - 30 days of DearDiary
3. What I learned building an app with AI
4. The case for boring technology in side projects
5. How to remember your life better
6. Self-hosting isn't hard, it's just unfamiliar
## Technical Notes
- The `build-blog.js` uses a simple regex-based Markdown converter
- For full Markdown support (tables, footnotes), consider using a proper parser
- Images should be hosted externally or added to the repo
- Code blocks work but syntax highlighting is limited

View File

@@ -0,0 +1,135 @@
---
title: Building DearDiary with AI - Lessons from Human-AI Collaboration
date: 2026-03-27
author: Konrad Lother
excerpt: What I learned from building a full-stack app using an AI coding assistant
---
# Building DearDiary with AI
## A Tale of Miscommunication and Debugging
I built DearDiary using an AI coding assistant. It was enlightening, frustrating, sometimes hilarious, and ultimately successful. Here's what I learned about human-AI collaboration.
## The Setup
DearDiary is a full-stack journaling app: Bun + Hono backend, React + Vite frontend, SQLite database, Docker deployment. Not trivial, but not rocket science either.
I gave the AI context about the project structure, my preferences, and let it work.
## The Problems We Hit
### 1. "It Should Work, But..."
The first major issue was the most classic: the AI made changes that *should* have worked according to its understanding, but didn't.
We consolidated environment variables into a single `.env` file with prefixes. The AI updated most references to `DATABASE_URL``BACKEND_DATABASE_URL`, but missed several:
- The Prisma schema
- The test helpers
- A variable in the healthcheck config
The app failed to start. The error message? Cryptic Prisma errors that took time to trace back to a simple env var mismatch.
**Lesson**: AI is great at systematic changes, but when it misses something, the gap is invisible to it. Always verify systematically.
### 2. The Disappearing Routes
The AI moved API routes into a separate file (`events.ts`) and mounted them at `/api/v1`. Simple, clean.
Except the routes were at `/api/v1/events` but the frontend was calling `/events`. The AI didn't catch that the mounting path was part of the route definition.
**Lesson**: AI understands code structure well, but context about how pieces connect across files is easily lost.
### 3. "I Fixed That"
Multiple times, the AI would say "Fixed!" and show the corrected code, but the actual file hadn't been changed. Or it would describe a solution that wasn't implemented.
This is the most dangerous mode of failure - confidence without execution.
**Lesson**: Never trust "fixed" without verification. Make it show you the actual changes.
### 4. Permission Denied
Docker entrypoint scripts kept failing with "permission denied". The AI knew about `chmod +x`, but the order of operations was wrong - file copied after chmod, or Docker cache serving old versions.
**Lesson**: AI knows facts, but execution order matters. Sometimes you need to walk through the sequence step by step.
### 5. The 404 Debugging Journey
Events returned 404. We checked:
1. Routes - correct
2. Mounting - fixed
3. Auth middleware - fixed
4. The actual problem: nginx port mapping. Port 3000 on the host was mapped directly to the backend, not through nginx. The frontend (served by nginx) couldn't reach the API.
**Lesson**: The AI focused on the obvious layers. The problem was in the infrastructure/configuration layer. AI needs explicit context about the full stack.
## What Went Well
Despite these issues, things also went surprisingly well:
- **Feature implementation**: The core features (event capture, AI generation, search) worked on first try
- **Consistency**: Once a pattern was established, the AI maintained it consistently
- **Refactoring**: Moving from multiple `.env` files to one was smooth after the initial issues
- **Documentation**: README updates, code comments, and AGENTS.md were accurate
## The Communication Patterns That Worked
### Be Specific About Failures
Instead of "it doesn't work", I'd say:
> "Events endpoint returns 404, checked docker logs and the route is registered"
The more context, the better the fix.
### Ask for Verification
> "Show me the exact changes you're making before committing"
This caught the "I said I fixed it" problem.
### Break Down Complex Changes
Instead of "consolidate all env vars", we did it in stages:
1. List all current env vars
2. Decide on naming convention
3. Update backend
4. Update frontend
5. Update docker-compose
6. Verify
### State What You Know Works
> "Previous similar changes worked with `docker compose build && docker compose up -d`"
Context about what has worked before helps the AI avoid untested approaches.
## The Meta-Lesson
Building with AI is like working with a very knowledgeable junior developer who:
- Has read every Stack Overflow post
- Can write code faster than you can type
- Sometimes confidently does the wrong thing
- Needs supervision, especially for changes spanning multiple files
- Gets better with clearer instructions
The key insight: **Your job becomes managing the AI, not just writing code.** You need to:
1. Provide good context
2. Verify systematically
3. Catch the invisible gaps
4. Maintain the mental model of the system
## What I'd Do Differently
1. **Track changes more carefully** - Use a changelog when AI makes changes, not just git diff
2. **Test incrementally** - Don't let the AI make 20 changes before testing
3. **Be clearer about expectations** - "This should work out of the box" is less clear than explicit test criteria
4. **Document the debugging journey** - The process of finding issues is valuable context for future fixes
## Conclusion
DearDiary is live. The AI and I built it together, argued about typos in environment variables, debugged at 2am, and shipped something I'm proud of.
Human-AI collaboration isn't about replacing programmers. It's about amplifying what humans do well (context, judgment, verification) with what AI does well (speed, consistency, pattern matching).
The future is not "AI replaces developers." It's "developers who use AI replace developers who don't."
Now go build something with AI. Just keep an eye on those env vars.
*— Konrad*

View File

@@ -0,0 +1,59 @@
---
title: Quick Start Guide
date: 2026-03-26
author: Konrad Lother
excerpt: How to get started with DearDiary in 5 minutes
---
# Quick Start Guide
Getting started with DearDiary takes about 5 minutes. Here's how:
## 1. Create an Event
Press **Ctrl+J** anywhere in the app to open the Quick Add widget. Type your event and press Enter.
That's it. It takes 3 seconds.
Try these:
- "Had coffee with Sarah at the new cafe downtown"
- "Finished the chapter on machine learning"
- "Rain started around 3pm, got soaked walking back"
## 2. Add More Events
Throughout your day, capture anything that feels worth remembering:
- Meetings and conversations
- Meals and what you ate
- Exercise and how you felt
- Thoughts and ideas
- Photos and voice memos
Don't overthink it. A short note is better than nothing.
## 3. Generate Your Diary
When you're ready, click the **Generate** button on today's page. AI reads all your events and writes a narrative diary entry.
You can:
- Regenerate with different instructions
- Add context by including previous days' diaries
- Edit the generated diary (but the events stay locked)
## 4. Review and Reflect
Read your generated diary. Does it capture the essence of your day? If not, regenerate with instructions like:
> "Focus more on the interesting conversations I had"
or
> "Make it more concise, highlight the key moments"
## Pro Tips
- **Be specific**: "Lunch with Marcus, talked about his new hiking trip to Patagonia" beats "Had lunch"
- **Capture emotions**: "Felt anxious before the presentation" is more interesting than "Presented to team"
- **Use voice**: Record voice memos while driving or walking - very efficient
That's it. Happy journaling!

200
www/build-blog.js Normal file
View File

@@ -0,0 +1,200 @@
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');
const postsDir = path.join(__dirname, '_posts');
const outputDir = path.join(__dirname, 'blog');
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Get all markdown files
const files = fs.readdirSync(postsDir).filter(f => f.endsWith('.md'));
// Parse and generate HTML for each post
const posts = files.map(file => {
const content = fs.readFileSync(path.join(postsDir, file), 'utf-8');
const { data: frontmatter, content: markdown } = matter(content);
// Simple markdown to HTML conversion
let html = markdown
// Headers
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
// Bold
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
// Italic
.replace(/\*(.*?)\*/g, '<em>$1</em>')
// Code blocks
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code class="$1">$2</code></pre>')
// Inline code
.replace(/`(.*?)`/g, '<code>$1</code>')
// Links
.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>')
// Images
.replace(/!\[(.*?)\]\((.*?)\)/g, '<img src="$2" alt="$1"/>')
// Line breaks
.replace(/\n\n/g, '</p><p>')
// Lists
.replace(/^\- (.*$)/gim, '<li>$1</li>')
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
// Wrap in paragraph
html = `<p>${html}</p>`;
// Clean up empty paragraphs
html = html.replace(/<p><\/p>/g, '');
const slug = file.replace('.md', '');
const url = `/blog/${slug}/`;
return {
...frontmatter,
slug,
url,
html,
date: frontmatter.date ? frontmatter.date.toISOString().split('T')[0] : ''
};
});
// Sort posts by date (newest first)
posts.sort((a, b) => new Date(b.date) - new Date(a.date));
const relativePath = process.env.GIT_URL ? '' : '../';
// Generate index page
const indexHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blog - DearDiary</title>
<link rel="stylesheet" href="${relativePath}css/styles.css">
<style>
.blog-container { max-width: 800px; margin: 0 auto; padding: 2rem; }
.blog-header { margin-bottom: 2rem; }
.blog-header h1 { font-size: 2rem; margin-bottom: 0.5rem; }
.blog-list { list-style: none; padding: 0; }
.blog-list li { margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid #334155; }
.blog-list h2 { margin: 0 0 0.5rem; font-size: 1.25rem; }
.blog-list h2 a { color: inherit; text-decoration: none; }
.blog-list h2 a:hover { color: #a855f7; }
.blog-meta { color: #64748b; font-size: 0.875rem; margin-bottom: 0.5rem; }
.blog-excerpt { color: #94a3b8; }
.blog-back { display: inline-block; margin-bottom: 1rem; color: #a855f7; text-decoration: none; }
.blog-back:hover { text-decoration: underline; }
</style>
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<a href="${relativePath}index.html" class="logo">
<svg width="32" height="32" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#6d28d9"/><stop offset="100%" style="stop-color:#4c1d95"/></linearGradient></defs><rect width="100" height="100" rx="20" fill="url(#g)"/><path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3"/><path d="M35 40 L65 40 M35 50 L65 50 M35 60 L55 60" stroke="white" stroke-width="2"/></svg>
DearDiary
</a>
<div class="nav-links">
<a href="${relativePath}index.html">Home</a>
<a href="${relativePath}docs/">Docs</a>
<a href="${relativePath}blog/">Blog</a>
<a href="${relativePath}index.html" class="btn btn-primary">Join Free Alpha</a>
</div>
</div>
</nav>
<div class="blog-container">
<div class="blog-header">
<h1>Blog</h1>
<p style="color: #94a3b8;">Updates, tutorials, and thoughts on AI-powered journaling.</p>
</div>
<ul class="blog-list">
${posts.map(post => `
<li>
<h2><a href="${post.url}">${post.title}</a></h2>
<div class="blog-meta">${post.date}${post.author ? ` · ${post.author}` : ''}</div>
${post.excerpt ? `<p class="blog-excerpt">${post.excerpt}</p>` : ''}
</li>
`).join('')}
</ul>
</div>
</body>
</html>`;
fs.writeFileSync(path.join(outputDir, 'index.html'), indexHtml);
// Generate individual post pages
posts.forEach(post => {
const postDir = path.join(outputDir, post.slug);
if (!fs.existsSync(postDir)) {
fs.mkdirSync(postDir, { recursive: true });
}
const postRelativePath = process.env.GIT_URL ? '' : '../../';
const postHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${post.title} - DearDiary Blog</title>
<link rel="stylesheet" href="${postRelativePath}css/styles.css">
<style>
.blog-container { max-width: 700px; margin: 0 auto; padding: 2rem; }
.blog-back { display: inline-block; margin-bottom: 1rem; color: #a855f7; text-decoration: none; }
.blog-back:hover { text-decoration: underline; }
.blog-header { margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 1px solid #334155; }
.blog-header h1 { margin: 0 0 0.5rem; }
.blog-meta { color: #64748b; font-size: 0.875rem; }
.blog-content h1, .blog-content h2, .blog-content h3 { margin-top: 1.5em; margin-bottom: 0.5em; color: #e2e8f0; }
.blog-content p { margin-bottom: 1em; line-height: 1.7; color: #cbd5e1; }
.blog-content a { color: #a855f7; }
.blog-content a:hover { text-decoration: underline; }
.blog-content code { background: #1e293b; padding: 0.2em 0.4em; border-radius: 4px; font-size: 0.9em; }
.blog-content pre { background: #1e293b; padding: 1em; border-radius: 8px; overflow-x: auto; margin: 1em 0; }
.blog-content pre code { background: none; padding: 0; }
.blog-content ul, .blog-content ol { margin: 1em 0; padding-left: 1.5em; color: #cbd5e1; }
.blog-content li { margin-bottom: 0.5em; line-height: 1.6; }
.blog-content img { max-width: 100%; height: auto; border-radius: 8px; margin: 1em 0; }
.blog-content blockquote { border-left: 3px solid #a855f7; padding-left: 1em; margin: 1em 0; color: #94a3b8; font-style: italic; }
</style>
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<a href="${postRelativePath}index.html" class="logo">
<svg width="32" height="32" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#6d28d9"/><stop offset="100%" style="stop-color:#4c1d95"/></linearGradient></defs><rect width="100" height="100" rx="20" fill="url(#g)"/><path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3"/><path d="M35 40 L65 40 M35 50 L65 50 M35 60 L55 60" stroke="white" stroke-width="2"/></svg>
DearDiary
</a>
<div class="nav-links">
<a href="${postRelativePath}index.html">Home</a>
<a href="${postRelativePath}docs/">Docs</a>
<a href="${postRelativePath}blog/">Blog</a>
<a href="${postRelativePath}index.html" class="btn btn-primary">Join Free Alpha</a>
</div>
</div>
</nav>
<div class="blog-container">
<a href="${postRelativePath}blog/" class="blog-back">← Back to Blog</a>
<article>
<header class="blog-header">
<h1>${post.title}</h1>
<div class="blog-meta">${post.date}${post.author ? ` · ${post.author}` : ''}</div>
</header>
<div class="blog-content">
${post.html}
</div>
</article>
</div>
</body>
</html>`;
fs.writeFileSync(path.join(postDir, 'index.html'), postHtml);
});
console.log(`Generated ${posts.length} blog posts.`);

123
www/package-lock.json generated Normal file
View File

@@ -0,0 +1,123 @@
{
"name": "deardiary-website",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "deardiary-website",
"version": "1.0.0",
"dependencies": {
"gray-matter": "^4.0.3"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"license": "MIT",
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"license": "MIT",
"dependencies": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"license": "MIT",
"dependencies": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"license": "BSD-3-Clause"
},
"node_modules/strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
}
}
}

7
www/package.json Normal file
View File

@@ -0,0 +1,7 @@
{
"name": "deardiary-website",
"version": "1.0.0",
"dependencies": {
"gray-matter": "^4.0.3"
}
}