@fydemy/cms 0.0.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/.env.local.example +20 -0
- package/.github/workflows/cd.yml +41 -0
- package/.github/workflows/ci.yml +43 -0
- package/CLOUDFLARE_R2_SETUP.md +92 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +80 -0
- package/README.md +293 -0
- package/SECURITY.md +79 -0
- package/package.json +21 -0
- package/packages/core/CHANGELOG.md +50 -0
- package/packages/core/QUICKSTART.md +80 -0
- package/packages/core/README.md +329 -0
- package/packages/core/SECURITY.md +79 -0
- package/packages/core/coverage/base.css +224 -0
- package/packages/core/coverage/block-navigation.js +87 -0
- package/packages/core/coverage/coverage-final.json +15 -0
- package/packages/core/coverage/favicon.png +0 -0
- package/packages/core/coverage/index.html +206 -0
- package/packages/core/coverage/prettify.css +1 -0
- package/packages/core/coverage/prettify.js +2 -0
- package/packages/core/coverage/sort-arrow-sprite.png +0 -0
- package/packages/core/coverage/sorter.js +210 -0
- package/packages/core/package.json +89 -0
- package/pnpm-workspace.yaml +3 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# CMS Authentication (Required)
|
|
2
|
+
CMS_ADMIN_USERNAME=admin
|
|
3
|
+
CMS_ADMIN_PASSWORD=your_secure_password_here
|
|
4
|
+
CMS_SESSION_SECRET=your-secret-key-must-be-at-least-32-characters-long
|
|
5
|
+
|
|
6
|
+
# ===== STORAGE OPTIONS (Choose one) =====
|
|
7
|
+
|
|
8
|
+
# Option 1: GitHub Storage (Production)
|
|
9
|
+
# Uncomment these if you want to use GitHub for storage
|
|
10
|
+
# GITHUB_TOKEN=ghp_your_github_token_here
|
|
11
|
+
# GITHUB_REPO=username/repository
|
|
12
|
+
# GITHUB_BRANCH=main
|
|
13
|
+
|
|
14
|
+
# Option 2: Cloudflare R2 Storage (Production)
|
|
15
|
+
# Uncomment these if you want to use Cloudflare R2 for storage
|
|
16
|
+
# CLOUDFLARE_ACCOUNT_ID=your-cloudflare-account-id
|
|
17
|
+
# CLOUDFLARE_ACCESS_KEY_ID=your-r2-access-key-id
|
|
18
|
+
# CLOUDFLARE_SECRET_ACCESS_KEY=your-r2-secret-access-key
|
|
19
|
+
# NEXT_PUBLIC_R2_PUBLIC_URL=https://your-bucket.r2.dev
|
|
20
|
+
# R2_BUCKET_NAME=my-cms-bucket
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CD
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: pnpm/action-setup@v3
|
|
18
|
+
with:
|
|
19
|
+
version: 9
|
|
20
|
+
|
|
21
|
+
- name: Use Node.js 20
|
|
22
|
+
uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: "20.x"
|
|
25
|
+
registry-url: "https://registry.npmjs.org"
|
|
26
|
+
cache: "pnpm"
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: pnpm install --frozen-lockfile
|
|
30
|
+
|
|
31
|
+
- name: Build
|
|
32
|
+
run: pnpm build
|
|
33
|
+
working-directory: packages/core
|
|
34
|
+
|
|
35
|
+
- name: Create .npmrc
|
|
36
|
+
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
|
|
37
|
+
working-directory: packages/core
|
|
38
|
+
|
|
39
|
+
- name: Publish to NPM
|
|
40
|
+
run: pnpm publish --access public --no-git-checks
|
|
41
|
+
working-directory: packages/core
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: [18.x, 20.x]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- uses: pnpm/action-setup@v3
|
|
21
|
+
with:
|
|
22
|
+
version: 9
|
|
23
|
+
|
|
24
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
25
|
+
uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: ${{ matrix.node-version }}
|
|
28
|
+
cache: "pnpm"
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: pnpm install --frozen-lockfile
|
|
32
|
+
|
|
33
|
+
- name: Type Check
|
|
34
|
+
run: pnpm type-check
|
|
35
|
+
working-directory: packages/core
|
|
36
|
+
|
|
37
|
+
- name: Run Tests
|
|
38
|
+
run: pnpm test
|
|
39
|
+
working-directory: packages/core
|
|
40
|
+
|
|
41
|
+
- name: Build
|
|
42
|
+
run: pnpm build
|
|
43
|
+
working-directory: packages/core
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Cloudflare R2 Storage Integration
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
The CMS has been updated to strictly support **Cloudflare R2** storage (removing references to S3 and Vercel Blob). The environment variables have been updated to match your requested format.
|
|
6
|
+
|
|
7
|
+
## Environment Variables
|
|
8
|
+
|
|
9
|
+
When setting up Cloudflare R2 storage, use these environment variables:
|
|
10
|
+
|
|
11
|
+
```env
|
|
12
|
+
# Cloudflare R2 Storage
|
|
13
|
+
CLOUDFLARE_ACCOUNT_ID=your-account-id
|
|
14
|
+
CLOUDFLARE_ACCESS_KEY_ID=your-access-key-id
|
|
15
|
+
CLOUDFLARE_SECRET_ACCESS_KEY=your-secret-access-key
|
|
16
|
+
NEXT_PUBLIC_R2_PUBLIC_URL=https://your-public-url.r2.dev
|
|
17
|
+
R2_BUCKET_NAME=my-bucket
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to Get These Values
|
|
21
|
+
|
|
22
|
+
### 1. CLOUDFLARE_ACCOUNT_ID
|
|
23
|
+
|
|
24
|
+
- Log in to your Cloudflare dashboard
|
|
25
|
+
- Your Account ID is visible in the URL or in the right sidebar
|
|
26
|
+
|
|
27
|
+
### 2. CLOUDFLARE_ACCESS_KEY_ID & CLOUDFLARE_SECRET_ACCESS_KEY
|
|
28
|
+
|
|
29
|
+
- Go to R2 in your Cloudflare dashboard
|
|
30
|
+
- Click "Manage R2 API Tokens"
|
|
31
|
+
- Create a new API token with read/write permissions
|
|
32
|
+
- Save both the Access Key ID and Secret Access Key
|
|
33
|
+
|
|
34
|
+
### 3. NEXT_PUBLIC_R2_PUBLIC_URL
|
|
35
|
+
|
|
36
|
+
- In your R2 bucket settings, go to "Settings"
|
|
37
|
+
- Under "Public Access", enable public access if needed
|
|
38
|
+
- Copy the public bucket URL (e.g., `https://pub-xxxxx.r2.dev`)
|
|
39
|
+
- Or set up a custom domain for your R2 bucket
|
|
40
|
+
|
|
41
|
+
### 4. R2_BUCKET_NAME
|
|
42
|
+
|
|
43
|
+
- The name of your R2 bucket (e.g., `my-cms-content`)
|
|
44
|
+
|
|
45
|
+
## Changes Made
|
|
46
|
+
|
|
47
|
+
### 1. `/packages/core/src/init/setup.ts`
|
|
48
|
+
|
|
49
|
+
- Updated storage provider selection to only show "GitHub" or "Cloudflare R2"
|
|
50
|
+
- Changed environment variable names from generic `STORAGE_*` to Cloudflare-specific names
|
|
51
|
+
- Removed references to S3 and Vercel Blob
|
|
52
|
+
|
|
53
|
+
### 2. `/packages/core/src/content/storage.ts`
|
|
54
|
+
|
|
55
|
+
- Added `CloudflareR2Storage` class that implements the `StorageProvider` interface
|
|
56
|
+
- Uses AWS S3 SDK (already installed) to communicate with Cloudflare R2
|
|
57
|
+
- Implements all required methods:
|
|
58
|
+
- `readFile()` - Read markdown files from R2
|
|
59
|
+
- `writeFile()` - Write markdown files to R2
|
|
60
|
+
- `deleteFile()` - Delete files from R2
|
|
61
|
+
- `listFiles()` - List files and directories in R2
|
|
62
|
+
- `exists()` - Check if a file exists
|
|
63
|
+
- `uploadFile()` - Upload binary files (images, PDFs, etc.)
|
|
64
|
+
- Updated `getStorageProvider()` to detect and use Cloudflare R2 when credentials are present
|
|
65
|
+
|
|
66
|
+
## Storage Provider Priority
|
|
67
|
+
|
|
68
|
+
The system will automatically select the storage provider in this order:
|
|
69
|
+
|
|
70
|
+
1. **Cloudflare R2** - If `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_ACCESS_KEY_ID`, and `CLOUDFLARE_SECRET_ACCESS_KEY` are set
|
|
71
|
+
2. **GitHub** - If `NODE_ENV=production` and `GITHUB_TOKEN` is set
|
|
72
|
+
3. **Local Storage** - Default for development
|
|
73
|
+
|
|
74
|
+
## File Structure in R2
|
|
75
|
+
|
|
76
|
+
- Content files: `content/your-file.md`
|
|
77
|
+
- Uploaded files: `uploads/filename.ext`
|
|
78
|
+
|
|
79
|
+
## Public URL Format
|
|
80
|
+
|
|
81
|
+
Uploaded files will be accessible at:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
{NEXT_PUBLIC_R2_PUBLIC_URL}/uploads/filename.ext
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Notes
|
|
88
|
+
|
|
89
|
+
- The AWS S3 SDK (`@aws-sdk/client-s3`) is already installed as a dependency
|
|
90
|
+
- Cloudflare R2 is S3-compatible, so we use the S3 client with R2 endpoints
|
|
91
|
+
- The implementation automatically sets the region to "auto" for R2
|
|
92
|
+
- Content-Type headers are automatically set for uploaded files
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Fydemy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/QUICKSTART.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @fydemy/cms - Quick Start
|
|
2
|
+
|
|
3
|
+
## What You Have
|
|
4
|
+
|
|
5
|
+
A complete, minimal CMS package for Next.js:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
📦 @fydemy/cms (packages/core)
|
|
9
|
+
- File-based markdown storage
|
|
10
|
+
- GitHub integration for production
|
|
11
|
+
- Simple auth (env username/password)
|
|
12
|
+
- TypeScript support
|
|
13
|
+
|
|
14
|
+
🚀 Demo App (apps/dev)
|
|
15
|
+
- Example Next.js application
|
|
16
|
+
- Admin dashboard at /admin
|
|
17
|
+
- Login page, editor, file manager
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Run the Demo
|
|
21
|
+
|
|
22
|
+
1. **Copy environment file:**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd apps/dev
|
|
26
|
+
cp .env.local.example .env.local
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. **Edit `.env.local`** with your credentials:
|
|
30
|
+
|
|
31
|
+
```env
|
|
32
|
+
CMS_ADMIN_USERNAME=admin
|
|
33
|
+
CMS_ADMIN_PASSWORD=your_password
|
|
34
|
+
CMS_SESSION_SECRET=your-32-char-secret
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. **Start development server:**
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cd ../..
|
|
41
|
+
pnpm dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
4. **Open browser:**
|
|
45
|
+
- Home: http://localhost:3000
|
|
46
|
+
- Admin: http://localhost:3000/admin
|
|
47
|
+
- Login with your credentials
|
|
48
|
+
|
|
49
|
+
## Use in Your Own Project
|
|
50
|
+
|
|
51
|
+
See [README.md](../README.md) for installation and setup instructions.
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- ✅ Create, edit, delete markdown files
|
|
56
|
+
- ✅ Frontmatter support (JSON)
|
|
57
|
+
- ✅ Session-based authentication
|
|
58
|
+
- ✅ Local storage (dev) + GitHub (production)
|
|
59
|
+
- ✅ TypeScript types included
|
|
60
|
+
- ✅ Vercel-compatible
|
|
61
|
+
|
|
62
|
+
## API Example
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { getMarkdownContent } from "@fydemy/cms";
|
|
66
|
+
|
|
67
|
+
// Read content
|
|
68
|
+
const post = await getMarkdownContent("example.md");
|
|
69
|
+
console.log(post.data.title); // "Example Post"
|
|
70
|
+
console.log(post.content); // markdown content
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Next Steps
|
|
74
|
+
|
|
75
|
+
1. Test the admin dashboard locally
|
|
76
|
+
2. Deploy to Vercel with GitHub token
|
|
77
|
+
3. Use the utilities in your pages
|
|
78
|
+
4. Customize the admin UI as needed
|
|
79
|
+
|
|
80
|
+
Built with TypeScript, minimal dependencies, zero database! 🎉
|
package/README.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# @fydemy/cms
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fydemy/cms)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
|
|
8
|
+
A minimal, secure, file-based CMS for Next.js without database requirements. Store content as markdown files with GitHub integration for production deployments.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- 📝 **File-based Storage** - Markdown files with frontmatter in `/public/content`
|
|
13
|
+
- 🔐 **Secure Authentication** - Timing-safe password comparison, rate limiting, input validation
|
|
14
|
+
- 🚀 **Vercel Compatible** - Deploy without any database setup
|
|
15
|
+
- 🐙 **GitHub Integration** - Automatic file commits in production
|
|
16
|
+
- 📦 **Zero Config** - Minimal setup required
|
|
17
|
+
- 🎯 **TypeScript First** - Full type safety with comprehensive type definitions
|
|
18
|
+
- ⚡ **Lightweight** - Small bundle size (~30KB), minimal dependencies
|
|
19
|
+
- 🛡️ **Security Hardened** - Built with security best practices
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @fydemy/cms
|
|
25
|
+
# or
|
|
26
|
+
pnpm add @fydemy/cms
|
|
27
|
+
# or
|
|
28
|
+
yarn add @fydemy/cms
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### 1. Initialize the CMS
|
|
34
|
+
|
|
35
|
+
Run the initialization command in your Next.js App Router project:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx fydemy-cms init
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This command will automatically:
|
|
42
|
+
|
|
43
|
+
- Create the content directory
|
|
44
|
+
- Scaffold Admin UI pages (`/app/admin`)
|
|
45
|
+
- Create API routes (`/app/api/cms`)
|
|
46
|
+
- Create a `.env.local.example` file
|
|
47
|
+
- Provide instructions for updating `middleware.ts`
|
|
48
|
+
|
|
49
|
+
### 2. Configure Environment
|
|
50
|
+
|
|
51
|
+
Copy `.env.local.example` to `.env.local` and set your credentials:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cp .env.local.example .env.local
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Update variables in `.env.local`:
|
|
58
|
+
|
|
59
|
+
```env
|
|
60
|
+
# Required for authentication
|
|
61
|
+
CMS_ADMIN_USERNAME=admin
|
|
62
|
+
CMS_ADMIN_PASSWORD=your_secure_password
|
|
63
|
+
CMS_SESSION_SECRET=your-secret-key-must-be-at-least-32-characters-long
|
|
64
|
+
|
|
65
|
+
# Optional: For production (GitHub integration)
|
|
66
|
+
GITHUB_TOKEN=ghp_your_github_token
|
|
67
|
+
GITHUB_REPO=username/repository
|
|
68
|
+
GITHUB_BRANCH=main
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> **Security Note**: Use strong passwords and keep `CMS_SESSION_SECRET` at least 32 characters long.
|
|
72
|
+
|
|
73
|
+
### 3. Read Content in Your App
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { getMarkdownContent } from "@fydemy/cms";
|
|
77
|
+
|
|
78
|
+
export default async function BlogPost({
|
|
79
|
+
params,
|
|
80
|
+
}: {
|
|
81
|
+
params: { slug: string };
|
|
82
|
+
}) {
|
|
83
|
+
const post = await getMarkdownContent(`${params.slug}.md`);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<article>
|
|
87
|
+
<h1>{post.data.title}</h1>
|
|
88
|
+
<p>{post.data.description}</p>
|
|
89
|
+
<div>{post.content}</div>
|
|
90
|
+
</article>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Security Features
|
|
96
|
+
|
|
97
|
+
### Built-in Security
|
|
98
|
+
|
|
99
|
+
- **Timing-Safe Authentication**: Uses `crypto.timingSafeEqual` to prevent timing attacks
|
|
100
|
+
- **Rate Limiting**: 5 login attempts per 15 minutes per IP address
|
|
101
|
+
- **Input Validation**: All inputs validated and sanitized
|
|
102
|
+
- **Path Validation**: Prevents directory traversal attacks
|
|
103
|
+
- **File Size Limits**: Default 10MB maximum file size
|
|
104
|
+
- **Secure Sessions**: httpOnly, sameSite, and secure cookies in production
|
|
105
|
+
- **No Username Enumeration**: Generic error messages
|
|
106
|
+
|
|
107
|
+
### Security Best Practices
|
|
108
|
+
|
|
109
|
+
1. **Strong Credentials**: Use strong, unique passwords for `CMS_ADMIN_PASSWORD`
|
|
110
|
+
2. **Secret Management**: Keep `CMS_SESSION_SECRET` at least 32 characters
|
|
111
|
+
3. **GitHub Token Security**: Use minimal permissions (only `repo` scope)
|
|
112
|
+
4. **HTTPS Only**: Always use HTTPS in production
|
|
113
|
+
5. **Regular Updates**: Keep dependencies up to date
|
|
114
|
+
6. **Environment Variables**: Never commit `.env` files
|
|
115
|
+
|
|
116
|
+
For more security information, see [SECURITY.md](./SECURITY.md).
|
|
117
|
+
|
|
118
|
+
## API Reference
|
|
119
|
+
|
|
120
|
+
### Content Management
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Read markdown file
|
|
124
|
+
const content = await getMarkdownContent("blog/post.md");
|
|
125
|
+
// Returns: { data: {...}, content: "..." }
|
|
126
|
+
|
|
127
|
+
// Write markdown file
|
|
128
|
+
await saveMarkdownContent(
|
|
129
|
+
"blog/post.md",
|
|
130
|
+
{ title: "My Post", date: "2024-01-01" },
|
|
131
|
+
"# Hello World"
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Delete file
|
|
135
|
+
await deleteMarkdownContent("blog/post.md");
|
|
136
|
+
|
|
137
|
+
// List files
|
|
138
|
+
const files = await listMarkdownFiles("blog");
|
|
139
|
+
// Returns: ['blog/post1.md', 'blog/post2.md']
|
|
140
|
+
|
|
141
|
+
// Check if file exists
|
|
142
|
+
const exists = await markdownFileExists("blog/post.md");
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Parsing Utilities
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { parseMarkdown, stringifyMarkdown } from "@fydemy/cms";
|
|
149
|
+
|
|
150
|
+
// Parse markdown string
|
|
151
|
+
const { data, content } = parseMarkdown(rawMarkdown);
|
|
152
|
+
|
|
153
|
+
// Convert to markdown
|
|
154
|
+
const markdown = stringifyMarkdown({ title: "Post" }, "Content here");
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Authentication
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { validateCredentials, createSession } from "@fydemy/cms";
|
|
161
|
+
|
|
162
|
+
// Validate credentials
|
|
163
|
+
const isValid = validateCredentials("admin", "password");
|
|
164
|
+
|
|
165
|
+
// Create session (returns JWT)
|
|
166
|
+
const token = await createSession("admin");
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Validation Utilities
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import {
|
|
173
|
+
validateFilePath,
|
|
174
|
+
validateUsername,
|
|
175
|
+
validatePassword,
|
|
176
|
+
sanitizeFrontmatter,
|
|
177
|
+
} from "@fydemy/cms";
|
|
178
|
+
|
|
179
|
+
// Validate file path (prevents directory traversal)
|
|
180
|
+
const safePath = validateFilePath("blog/post.md");
|
|
181
|
+
|
|
182
|
+
// Validate username
|
|
183
|
+
validateUsername("admin"); // throws if invalid
|
|
184
|
+
|
|
185
|
+
// Sanitize frontmatter data
|
|
186
|
+
const safe = sanitizeFrontmatter({ title: "Test", script: "<script>" });
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Storage
|
|
190
|
+
|
|
191
|
+
### Development
|
|
192
|
+
|
|
193
|
+
Files are stored locally in `/public/content` directory.
|
|
194
|
+
|
|
195
|
+
### Production
|
|
196
|
+
|
|
197
|
+
When `NODE_ENV=production` and `GITHUB_TOKEN` is set, all file operations are performed via GitHub API, creating commits directly to your repository.
|
|
198
|
+
|
|
199
|
+
## Environment Variables
|
|
200
|
+
|
|
201
|
+
| Variable | Required | Description |
|
|
202
|
+
| -------------------- | ---------- | ------------------------------- |
|
|
203
|
+
| `CMS_ADMIN_USERNAME` | Yes | Admin username |
|
|
204
|
+
| `CMS_ADMIN_PASSWORD` | Yes | Admin password |
|
|
205
|
+
| `CMS_SESSION_SECRET` | Yes | JWT secret (min 32 chars) |
|
|
206
|
+
| `GITHUB_TOKEN` | Production | GitHub personal access token |
|
|
207
|
+
| `GITHUB_REPO` | Production | Repository (format: owner/repo) |
|
|
208
|
+
| `GITHUB_BRANCH` | Production | Branch name (default: main) |
|
|
209
|
+
|
|
210
|
+
## GitHub Setup
|
|
211
|
+
|
|
212
|
+
1. Create a GitHub Personal Access Token with `repo` permissions
|
|
213
|
+
2. Add the token to your environment variables
|
|
214
|
+
3. Deploy to Vercel and configure the environment variables
|
|
215
|
+
|
|
216
|
+
## FAQ
|
|
217
|
+
|
|
218
|
+
### Is this suitable for production?
|
|
219
|
+
|
|
220
|
+
Yes! The package includes security hardening, rate limiting, and has been tested for production use. Make sure to follow security best practices.
|
|
221
|
+
|
|
222
|
+
### Can I use this with other frameworks?
|
|
223
|
+
|
|
224
|
+
This package is designed for Next.js App Router (13+). For other frameworks, you can use the core utilities but will need to implement your own API routes.
|
|
225
|
+
|
|
226
|
+
### How do I customize the file size limit?
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { MAX_FILE_SIZE } from "@fydemy/cms";
|
|
230
|
+
// Default is 10MB, you can check this constant
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
To change it, you'll need to implement your own validation layer.
|
|
234
|
+
|
|
235
|
+
### Does it support images?
|
|
236
|
+
|
|
237
|
+
Yes! The package includes file upload functionality. Images can be uploaded and stored in `/public/uploads` (local) or via GitHub API (production).
|
|
238
|
+
|
|
239
|
+
### How do I backup my content?
|
|
240
|
+
|
|
241
|
+
Since content is stored in your GitHub repository (in production), it's automatically backed up with full version history. In development, the `/public/content` directory can be committed to git.
|
|
242
|
+
|
|
243
|
+
### What about rate limiting in production?
|
|
244
|
+
|
|
245
|
+
The built-in rate limiter is memory-based and resets on server restart. For production with multiple instances, consider implementing Redis-based rate limiting.
|
|
246
|
+
|
|
247
|
+
### Can I add more admin users?
|
|
248
|
+
|
|
249
|
+
Currently, the package supports a single admin user via environment variables. For multi-user support, you'd need to implement a custom authentication layer.
|
|
250
|
+
|
|
251
|
+
## Example Admin UI
|
|
252
|
+
|
|
253
|
+
Check the `/apps/dev` directory in this repository for a complete example with:
|
|
254
|
+
|
|
255
|
+
- Login page
|
|
256
|
+
- Admin dashboard
|
|
257
|
+
- File editor
|
|
258
|
+
- File management
|
|
259
|
+
|
|
260
|
+
## Troubleshooting
|
|
261
|
+
|
|
262
|
+
### "CMS_SESSION_SECRET must be at least 32 characters"
|
|
263
|
+
|
|
264
|
+
Make sure your session secret is long enough. Generate a secure random string:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Rate limiting not working across restarts
|
|
271
|
+
|
|
272
|
+
The rate limiter is in-memory. For persistent rate limiting, implement Redis storage.
|
|
273
|
+
|
|
274
|
+
### GitHub API rate limits
|
|
275
|
+
|
|
276
|
+
GitHub API has rate limits. For high-traffic sites, consider caching content or using a CDN.
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT
|
|
281
|
+
|
|
282
|
+
## Contributing
|
|
283
|
+
|
|
284
|
+
Contributions welcome! This is a minimal CMS focused on simplicity and maintainability.
|
|
285
|
+
|
|
286
|
+
Please report security vulnerabilities privately to fydemy@gmail.com or via GitHub security advisories.
|
|
287
|
+
|
|
288
|
+
## Links
|
|
289
|
+
|
|
290
|
+
- [GitHub Repository](https://github.com/fydemy/cms)
|
|
291
|
+
- [npm Package](https://www.npmjs.com/package/@fydemy/cms)
|
|
292
|
+
- [Report Issues](https://github.com/fydemy/cms/issues)
|
|
293
|
+
- [Security Policy](https://github.com/fydemy/cms/blob/main/SECURITY.md)
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
We release patches for security vulnerabilities for the following versions:
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
| ------- | ------------------ |
|
|
9
|
+
| 1.x.x | :white_check_mark: |
|
|
10
|
+
| < 1.0 | :x: |
|
|
11
|
+
|
|
12
|
+
## Reporting a Vulnerability
|
|
13
|
+
|
|
14
|
+
**Please do not report security vulnerabilities through public GitHub issues.**
|
|
15
|
+
|
|
16
|
+
If you discover a security vulnerability in @fydemy/cms, please report it by emailing fydemy@gmail.com (or create a private security advisory on GitHub).
|
|
17
|
+
|
|
18
|
+
Please include the following information:
|
|
19
|
+
|
|
20
|
+
- Type of vulnerability
|
|
21
|
+
- Full paths of source file(s) related to the vulnerability
|
|
22
|
+
- Location of the affected source code (tag/branch/commit or direct URL)
|
|
23
|
+
- Step-by-step instructions to reproduce the issue
|
|
24
|
+
- Proof-of-concept or exploit code (if possible)
|
|
25
|
+
- Impact of the issue, including how an attacker might exploit it
|
|
26
|
+
|
|
27
|
+
We will acknowledge your email within 48 hours and send a more detailed response within 7 days indicating the next steps in handling your report.
|
|
28
|
+
|
|
29
|
+
## Security Best Practices
|
|
30
|
+
|
|
31
|
+
When using @fydemy/cms, follow these security best practices:
|
|
32
|
+
|
|
33
|
+
### Environment Variables
|
|
34
|
+
|
|
35
|
+
- Use strong, random values for `CMS_SESSION_SECRET` (minimum 32 characters)
|
|
36
|
+
- Use strong passwords for `CMS_ADMIN_PASSWORD`
|
|
37
|
+
- Never commit `.env` files to version control
|
|
38
|
+
- Rotate credentials regularly
|
|
39
|
+
|
|
40
|
+
### GitHub Token
|
|
41
|
+
|
|
42
|
+
- Use GitHub Personal Access Tokens with minimal required permissions (only `repo` scope)
|
|
43
|
+
- Use fine-grained personal access tokens when possible
|
|
44
|
+
- Store tokens securely in your deployment platform's secret management
|
|
45
|
+
|
|
46
|
+
### Network Security
|
|
47
|
+
|
|
48
|
+
- Always use HTTPS in production
|
|
49
|
+
- Configure your Next.js app behind a reverse proxy or CDN
|
|
50
|
+
- Enable security headers (CSP, HSTS, X-Frame-Options, etc.)
|
|
51
|
+
|
|
52
|
+
### Rate Limiting
|
|
53
|
+
|
|
54
|
+
- The built-in rate limiter is memory-based and resets on server restart
|
|
55
|
+
- For production, consider implementing Redis-based rate limiting
|
|
56
|
+
- Monitor failed login attempts
|
|
57
|
+
|
|
58
|
+
### File Uploads
|
|
59
|
+
|
|
60
|
+
- The default file size limit is 10MB
|
|
61
|
+
- Validate file types on the client and server
|
|
62
|
+
- Consider implementing virus scanning for uploaded files
|
|
63
|
+
- Use Content Security Policy headers to prevent XSS from uploaded content
|
|
64
|
+
|
|
65
|
+
### Session Security
|
|
66
|
+
|
|
67
|
+
- Session cookies are httpOnly, sameSite=lax, and secure in production
|
|
68
|
+
- Sessions expire after 7 days
|
|
69
|
+
- Consider shorter session duration for sensitive applications
|
|
70
|
+
|
|
71
|
+
## Disclosure Policy
|
|
72
|
+
|
|
73
|
+
When we receive a security bug report, we will:
|
|
74
|
+
|
|
75
|
+
1. Confirm the problem and determine affected versions
|
|
76
|
+
2. Audit code to find similar problems
|
|
77
|
+
3. Prepare fixes for all supported versions
|
|
78
|
+
4. Release new versions as soon as possible
|
|
79
|
+
5. Credit the reporter (unless they prefer to remain anonymous)
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fydemy/cms",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Simple file-based CMS for Next.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "pnpm --filter dev dev",
|
|
7
|
+
"build": "pnpm --filter @fydemy/cms build",
|
|
8
|
+
"build:all": "pnpm -r build"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"cms",
|
|
12
|
+
"nextjs",
|
|
13
|
+
"markdown",
|
|
14
|
+
"vercel"
|
|
15
|
+
],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.3.3"
|
|
20
|
+
}
|
|
21
|
+
}
|