@fydemy/cms 1.0.0 → 1.0.2
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/README.md +310 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +199 -0
- package/dist/bin.js.map +1 -0
- package/dist/bin.mjs +176 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1196 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1194 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -1
package/README.md
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
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. Update Middleware
|
|
74
|
+
|
|
75
|
+
The init command will guide you to update `middleware.ts` to protect admin routes:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { createAuthMiddleware } from "@fydemy/cms";
|
|
79
|
+
import { NextRequest } from "next/server";
|
|
80
|
+
|
|
81
|
+
export function middleware(request: NextRequest) {
|
|
82
|
+
return createAuthMiddleware()(request);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const config = {
|
|
86
|
+
matcher: ["/admin/:path*"],
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 4. Read Content in Your App
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { getMarkdownContent } from "@fydemy/cms";
|
|
94
|
+
|
|
95
|
+
export default async function BlogPost({
|
|
96
|
+
params,
|
|
97
|
+
}: {
|
|
98
|
+
params: { slug: string };
|
|
99
|
+
}) {
|
|
100
|
+
const post = await getMarkdownContent(`${params.slug}.md`);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<article>
|
|
104
|
+
<h1>{post.data.title}</h1>
|
|
105
|
+
<p>{post.data.description}</p>
|
|
106
|
+
<div>{post.content}</div>
|
|
107
|
+
</article>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Security Features
|
|
113
|
+
|
|
114
|
+
### Built-in Security
|
|
115
|
+
|
|
116
|
+
- **Timing-Safe Authentication**: Uses `crypto.timingSafeEqual` to prevent timing attacks
|
|
117
|
+
- **Rate Limiting**: 5 login attempts per 15 minutes per IP address
|
|
118
|
+
- **Input Validation**: All inputs validated and sanitized
|
|
119
|
+
- **Path Validation**: Prevents directory traversal attacks
|
|
120
|
+
- **File Size Limits**: Default 10MB maximum file size
|
|
121
|
+
- **Secure Sessions**: httpOnly, sameSite, and secure cookies in production
|
|
122
|
+
- **No Username Enumeration**: Generic error messages
|
|
123
|
+
|
|
124
|
+
### Security Best Practices
|
|
125
|
+
|
|
126
|
+
1. **Strong Credentials**: Use strong, unique passwords for `CMS_ADMIN_PASSWORD`
|
|
127
|
+
2. **Secret Management**: Keep `CMS_SESSION_SECRET` at least 32 characters
|
|
128
|
+
3. **GitHub Token Security**: Use minimal permissions (only `repo` scope)
|
|
129
|
+
4. **HTTPS Only**: Always use HTTPS in production
|
|
130
|
+
5. **Regular Updates**: Keep dependencies up to date
|
|
131
|
+
6. **Environment Variables**: Never commit `.env` files
|
|
132
|
+
|
|
133
|
+
For more security information, see [SECURITY.md](./SECURITY.md).
|
|
134
|
+
|
|
135
|
+
## API Reference
|
|
136
|
+
|
|
137
|
+
### Content Management
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Read markdown file
|
|
141
|
+
const content = await getMarkdownContent("blog/post.md");
|
|
142
|
+
// Returns: { data: {...}, content: "..." }
|
|
143
|
+
|
|
144
|
+
// Write markdown file
|
|
145
|
+
await saveMarkdownContent(
|
|
146
|
+
"blog/post.md",
|
|
147
|
+
{ title: "My Post", date: "2024-01-01" },
|
|
148
|
+
"# Hello World"
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Delete file
|
|
152
|
+
await deleteMarkdownContent("blog/post.md");
|
|
153
|
+
|
|
154
|
+
// List files
|
|
155
|
+
const files = await listMarkdownFiles("blog");
|
|
156
|
+
// Returns: ['blog/post1.md', 'blog/post2.md']
|
|
157
|
+
|
|
158
|
+
// Check if file exists
|
|
159
|
+
const exists = await markdownFileExists("blog/post.md");
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Parsing Utilities
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { parseMarkdown, stringifyMarkdown } from "@fydemy/cms";
|
|
166
|
+
|
|
167
|
+
// Parse markdown string
|
|
168
|
+
const { data, content } = parseMarkdown(rawMarkdown);
|
|
169
|
+
|
|
170
|
+
// Convert to markdown
|
|
171
|
+
const markdown = stringifyMarkdown({ title: "Post" }, "Content here");
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Authentication
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { validateCredentials, createSession } from "@fydemy/cms";
|
|
178
|
+
|
|
179
|
+
// Validate credentials
|
|
180
|
+
const isValid = validateCredentials("admin", "password");
|
|
181
|
+
|
|
182
|
+
// Create session (returns JWT)
|
|
183
|
+
const token = await createSession("admin");
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Validation Utilities
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import {
|
|
190
|
+
validateFilePath,
|
|
191
|
+
validateUsername,
|
|
192
|
+
validatePassword,
|
|
193
|
+
sanitizeFrontmatter,
|
|
194
|
+
} from "@fydemy/cms";
|
|
195
|
+
|
|
196
|
+
// Validate file path (prevents directory traversal)
|
|
197
|
+
const safePath = validateFilePath("blog/post.md");
|
|
198
|
+
|
|
199
|
+
// Validate username
|
|
200
|
+
validateUsername("admin"); // throws if invalid
|
|
201
|
+
|
|
202
|
+
// Sanitize frontmatter data
|
|
203
|
+
const safe = sanitizeFrontmatter({ title: "Test", script: "<script>" });
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Storage
|
|
207
|
+
|
|
208
|
+
### Development
|
|
209
|
+
|
|
210
|
+
Files are stored locally in `/public/content` directory.
|
|
211
|
+
|
|
212
|
+
### Production
|
|
213
|
+
|
|
214
|
+
When `NODE_ENV=production` and `GITHUB_TOKEN` is set, all file operations are performed via GitHub API, creating commits directly to your repository.
|
|
215
|
+
|
|
216
|
+
## Environment Variables
|
|
217
|
+
|
|
218
|
+
| Variable | Required | Description |
|
|
219
|
+
| -------------------- | ---------- | ------------------------------- |
|
|
220
|
+
| `CMS_ADMIN_USERNAME` | Yes | Admin username |
|
|
221
|
+
| `CMS_ADMIN_PASSWORD` | Yes | Admin password |
|
|
222
|
+
| `CMS_SESSION_SECRET` | Yes | JWT secret (min 32 chars) |
|
|
223
|
+
| `GITHUB_TOKEN` | Production | GitHub personal access token |
|
|
224
|
+
| `GITHUB_REPO` | Production | Repository (format: owner/repo) |
|
|
225
|
+
| `GITHUB_BRANCH` | Production | Branch name (default: main) |
|
|
226
|
+
|
|
227
|
+
## GitHub Setup
|
|
228
|
+
|
|
229
|
+
1. Create a GitHub Personal Access Token with `repo` permissions
|
|
230
|
+
2. Add the token to your environment variables
|
|
231
|
+
3. Deploy to Vercel and configure the environment variables
|
|
232
|
+
|
|
233
|
+
## FAQ
|
|
234
|
+
|
|
235
|
+
### Is this suitable for production?
|
|
236
|
+
|
|
237
|
+
Yes! The package includes security hardening, rate limiting, and has been tested for production use. Make sure to follow security best practices.
|
|
238
|
+
|
|
239
|
+
### Can I use this with other frameworks?
|
|
240
|
+
|
|
241
|
+
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.
|
|
242
|
+
|
|
243
|
+
### How do I customize the file size limit?
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { MAX_FILE_SIZE } from "@fydemy/cms";
|
|
247
|
+
// Default is 10MB, you can check this constant
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
To change it, you'll need to implement your own validation layer.
|
|
251
|
+
|
|
252
|
+
### Does it support images?
|
|
253
|
+
|
|
254
|
+
Yes! The package includes file upload functionality. Images can be uploaded and stored in `/public/uploads` (local) or via GitHub API (production).
|
|
255
|
+
|
|
256
|
+
### How do I backup my content?
|
|
257
|
+
|
|
258
|
+
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.
|
|
259
|
+
|
|
260
|
+
### What about rate limiting in production?
|
|
261
|
+
|
|
262
|
+
The built-in rate limiter is memory-based and resets on server restart. For production with multiple instances, consider implementing Redis-based rate limiting.
|
|
263
|
+
|
|
264
|
+
### Can I add more admin users?
|
|
265
|
+
|
|
266
|
+
Currently, the package supports a single admin user via environment variables. For multi-user support, you'd need to implement a custom authentication layer.
|
|
267
|
+
|
|
268
|
+
## Example Admin UI
|
|
269
|
+
|
|
270
|
+
Check the `/apps/dev` directory in this repository for a complete example with:
|
|
271
|
+
|
|
272
|
+
- Login page
|
|
273
|
+
- Admin dashboard
|
|
274
|
+
- File editor
|
|
275
|
+
- File management
|
|
276
|
+
|
|
277
|
+
## Troubleshooting
|
|
278
|
+
|
|
279
|
+
### "CMS_SESSION_SECRET must be at least 32 characters"
|
|
280
|
+
|
|
281
|
+
Make sure your session secret is long enough. Generate a secure random string:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Rate limiting not working across restarts
|
|
288
|
+
|
|
289
|
+
The rate limiter is in-memory. For persistent rate limiting, implement Redis storage.
|
|
290
|
+
|
|
291
|
+
### GitHub API rate limits
|
|
292
|
+
|
|
293
|
+
GitHub API has rate limits. For high-traffic sites, consider caching content or using a CDN.
|
|
294
|
+
|
|
295
|
+
## License
|
|
296
|
+
|
|
297
|
+
MIT
|
|
298
|
+
|
|
299
|
+
## Contributing
|
|
300
|
+
|
|
301
|
+
Contributions welcome! This is a minimal CMS focused on simplicity and maintainability.
|
|
302
|
+
|
|
303
|
+
Please report security vulnerabilities privately to fydemy@gmail.com or via GitHub security advisories.
|
|
304
|
+
|
|
305
|
+
## Links
|
|
306
|
+
|
|
307
|
+
- [GitHub Repository](https://github.com/fydemy/cms)
|
|
308
|
+
- [npm Package](https://www.npmjs.com/package/@fydemy/cms)
|
|
309
|
+
- [Report Issues](https://github.com/fydemy/cms/issues)
|
|
310
|
+
- [Security Policy](https://github.com/fydemy/cms/blob/main/SECURITY.md)
|
package/dist/bin.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/init/setup.ts
|
|
27
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
28
|
+
var import_path = __toESM(require("path"));
|
|
29
|
+
async function initCMS(config = {}) {
|
|
30
|
+
const contentDir = config.contentDir || "public/content";
|
|
31
|
+
const fullPath = import_path.default.join(process.cwd(), contentDir);
|
|
32
|
+
const appDir = import_path.default.join(process.cwd(), "app");
|
|
33
|
+
try {
|
|
34
|
+
await import_promises.default.access(appDir);
|
|
35
|
+
} catch {
|
|
36
|
+
console.error(
|
|
37
|
+
'\u274C Error: "app" directory not found. This init script requires Next.js App Router.'
|
|
38
|
+
);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log("\u{1F680} Initializing @fydemy/cms...");
|
|
42
|
+
await import_promises.default.mkdir(fullPath, { recursive: true });
|
|
43
|
+
const exampleContent = `---
|
|
44
|
+
title: Example Post
|
|
45
|
+
description: This is an example markdown file
|
|
46
|
+
date: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
# Welcome to your CMS!
|
|
50
|
+
|
|
51
|
+
This is an example markdown file. You can edit or delete it from the admin dashboard.
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- File-based content storage
|
|
56
|
+
- Markdown with frontmatter
|
|
57
|
+
- GitHub integration for production
|
|
58
|
+
- Simple authentication
|
|
59
|
+
- No database required
|
|
60
|
+
`;
|
|
61
|
+
const examplePath = import_path.default.join(fullPath, "example.md");
|
|
62
|
+
await import_promises.default.writeFile(examplePath, exampleContent, "utf-8");
|
|
63
|
+
console.log("\u2705 Created content directory and example file");
|
|
64
|
+
const adminDir = import_path.default.join(appDir, "admin");
|
|
65
|
+
const loginDir = import_path.default.join(adminDir, "login");
|
|
66
|
+
await import_promises.default.mkdir(loginDir, { recursive: true });
|
|
67
|
+
await import_promises.default.writeFile(
|
|
68
|
+
import_path.default.join(adminDir, "page.tsx"),
|
|
69
|
+
`import { AdminDashboard } from '@fydemy/cms';
|
|
70
|
+
|
|
71
|
+
export default AdminDashboard;
|
|
72
|
+
`,
|
|
73
|
+
"utf-8"
|
|
74
|
+
);
|
|
75
|
+
await import_promises.default.writeFile(
|
|
76
|
+
import_path.default.join(loginDir, "page.tsx"),
|
|
77
|
+
`import { Login } from '@fydemy/cms';
|
|
78
|
+
|
|
79
|
+
export default Login;
|
|
80
|
+
`,
|
|
81
|
+
"utf-8"
|
|
82
|
+
);
|
|
83
|
+
console.log("\u2705 Created Admin UI pages");
|
|
84
|
+
const apiCmsDir = import_path.default.join(appDir, "api", "cms");
|
|
85
|
+
await import_promises.default.mkdir(import_path.default.join(apiCmsDir, "login"), { recursive: true });
|
|
86
|
+
await import_promises.default.writeFile(
|
|
87
|
+
import_path.default.join(apiCmsDir, "login", "route.ts"),
|
|
88
|
+
`import { handleLogin } from '@fydemy/cms';
|
|
89
|
+
export { handleLogin as POST };
|
|
90
|
+
`,
|
|
91
|
+
"utf-8"
|
|
92
|
+
);
|
|
93
|
+
await import_promises.default.mkdir(import_path.default.join(apiCmsDir, "logout"), { recursive: true });
|
|
94
|
+
await import_promises.default.writeFile(
|
|
95
|
+
import_path.default.join(apiCmsDir, "logout", "route.ts"),
|
|
96
|
+
`import { handleLogout } from '@fydemy/cms';
|
|
97
|
+
export { handleLogout as POST };
|
|
98
|
+
`,
|
|
99
|
+
"utf-8"
|
|
100
|
+
);
|
|
101
|
+
await import_promises.default.mkdir(import_path.default.join(apiCmsDir, "upload"), { recursive: true });
|
|
102
|
+
await import_promises.default.writeFile(
|
|
103
|
+
import_path.default.join(apiCmsDir, "upload", "route.ts"),
|
|
104
|
+
`import { handleUpload } from '@fydemy/cms';
|
|
105
|
+
export { handleUpload as POST };
|
|
106
|
+
`,
|
|
107
|
+
"utf-8"
|
|
108
|
+
);
|
|
109
|
+
await import_promises.default.mkdir(import_path.default.join(apiCmsDir, "list", "[[...path]]"), {
|
|
110
|
+
recursive: true
|
|
111
|
+
});
|
|
112
|
+
await import_promises.default.writeFile(
|
|
113
|
+
import_path.default.join(apiCmsDir, "list", "[[...path]]", "route.ts"),
|
|
114
|
+
`import { createListApiHandlers } from '@fydemy/cms';
|
|
115
|
+
|
|
116
|
+
const handlers = createListApiHandlers();
|
|
117
|
+
export const GET = handlers.GET;
|
|
118
|
+
`,
|
|
119
|
+
"utf-8"
|
|
120
|
+
);
|
|
121
|
+
await import_promises.default.mkdir(import_path.default.join(apiCmsDir, "content", "[...path]"), {
|
|
122
|
+
recursive: true
|
|
123
|
+
});
|
|
124
|
+
await import_promises.default.writeFile(
|
|
125
|
+
import_path.default.join(apiCmsDir, "content", "[...path]", "route.ts"),
|
|
126
|
+
`import { createContentApiHandlers } from '@fydemy/cms';
|
|
127
|
+
|
|
128
|
+
const handlers = createContentApiHandlers();
|
|
129
|
+
export const GET = handlers.GET;
|
|
130
|
+
export const POST = handlers.POST;
|
|
131
|
+
export const DELETE = handlers.DELETE;
|
|
132
|
+
`,
|
|
133
|
+
"utf-8"
|
|
134
|
+
);
|
|
135
|
+
console.log("\u2705 Created API routes");
|
|
136
|
+
const middlewarePath = import_path.default.join(process.cwd(), "middleware.ts");
|
|
137
|
+
try {
|
|
138
|
+
await import_promises.default.access(middlewarePath);
|
|
139
|
+
console.log(
|
|
140
|
+
"\u26A0\uFE0F middleware.ts already exists. Please manually add the CMS auth middleware:"
|
|
141
|
+
);
|
|
142
|
+
console.log(`
|
|
143
|
+
import { createAuthMiddleware } from '@fydemy/cms';
|
|
144
|
+
// ... existing imports
|
|
145
|
+
|
|
146
|
+
export function middleware(request: NextRequest) {
|
|
147
|
+
// Add this:
|
|
148
|
+
const authResponse = createAuthMiddleware()(request);
|
|
149
|
+
if (authResponse) return authResponse;
|
|
150
|
+
|
|
151
|
+
// ... existing middleware logic
|
|
152
|
+
}
|
|
153
|
+
`);
|
|
154
|
+
} catch {
|
|
155
|
+
await import_promises.default.writeFile(
|
|
156
|
+
middlewarePath,
|
|
157
|
+
`import { createAuthMiddleware } from '@fydemy/cms';
|
|
158
|
+
import { NextRequest } from 'next/server';
|
|
159
|
+
|
|
160
|
+
export function middleware(request: NextRequest) {
|
|
161
|
+
return createAuthMiddleware()(request);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const config = {
|
|
165
|
+
matcher: ['/admin/:path*'],
|
|
166
|
+
};
|
|
167
|
+
`,
|
|
168
|
+
"utf-8"
|
|
169
|
+
);
|
|
170
|
+
console.log("\u2705 Created middleware.ts");
|
|
171
|
+
}
|
|
172
|
+
const envExamplePath = import_path.default.join(process.cwd(), ".env.local.example");
|
|
173
|
+
await import_promises.default.writeFile(
|
|
174
|
+
envExamplePath,
|
|
175
|
+
`CMS_ADMIN_USERNAME=admin
|
|
176
|
+
CMS_ADMIN_PASSWORD=password
|
|
177
|
+
CMS_SESSION_SECRET=ensure_this_is_at_least_32_chars_long_random_string
|
|
178
|
+
|
|
179
|
+
# GitHub Storage (Production)
|
|
180
|
+
GITHUB_TOKEN=
|
|
181
|
+
GITHUB_REPO=owner/repo
|
|
182
|
+
GITHUB_BRANCH=main
|
|
183
|
+
`,
|
|
184
|
+
"utf-8"
|
|
185
|
+
);
|
|
186
|
+
console.log("");
|
|
187
|
+
console.log("\u{1F389} CMS initialized successfully!");
|
|
188
|
+
console.log(
|
|
189
|
+
"1. Copy .env.local.example to .env.local and set your credentials"
|
|
190
|
+
);
|
|
191
|
+
console.log("2. Run your dev server and visit /admin");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/bin.ts
|
|
195
|
+
initCMS().catch((err) => {
|
|
196
|
+
console.error("Error initializing CMS:", err);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|
|
199
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init/setup.ts","../src/bin.ts"],"sourcesContent":["import fs from \"fs/promises\";\nimport path from \"path\";\n\nexport interface InitCMSConfig {\n /** Directory where content is stored (default: \"public/content\") */\n contentDir?: string;\n}\n\n/**\n * Initialize CMS in a Next.js project\n * Creates the content directory and an example markdown file.\n * @param config - Configuration options\n */\nexport async function initCMS(config: InitCMSConfig = {}) {\n const contentDir = config.contentDir || \"public/content\";\n const fullPath = path.join(process.cwd(), contentDir);\n const appDir = path.join(process.cwd(), \"app\");\n\n // Check if we are in a Next.js App Router project\n try {\n await fs.access(appDir);\n } catch {\n console.error(\n '❌ Error: \"app\" directory not found. This init script requires Next.js App Router.'\n );\n return;\n }\n\n console.log(\"🚀 Initializing @fydemy/cms...\");\n\n // 1. Create content directory and example file\n await fs.mkdir(fullPath, { recursive: true });\n\n const exampleContent = `---\ntitle: Example Post\ndescription: This is an example markdown file\ndate: ${new Date().toISOString()}\n---\n\n# Welcome to your CMS!\n\nThis is an example markdown file. You can edit or delete it from the admin dashboard.\n\n## Features\n\n- File-based content storage\n- Markdown with frontmatter\n- GitHub integration for production\n- Simple authentication\n- No database required\n`;\n\n const examplePath = path.join(fullPath, \"example.md\");\n await fs.writeFile(examplePath, exampleContent, \"utf-8\");\n console.log(\"✅ Created content directory and example file\");\n\n // 2. Scaffold Admin Pages\n const adminDir = path.join(appDir, \"admin\");\n const loginDir = path.join(adminDir, \"login\");\n\n await fs.mkdir(loginDir, { recursive: true });\n\n await fs.writeFile(\n path.join(adminDir, \"page.tsx\"),\n `import { AdminDashboard } from '@fydemy/cms';\\n\\nexport default AdminDashboard;\\n`,\n \"utf-8\"\n );\n\n await fs.writeFile(\n path.join(loginDir, \"page.tsx\"),\n `import { Login } from '@fydemy/cms';\\n\\nexport default Login;\\n`,\n \"utf-8\"\n );\n console.log(\"✅ Created Admin UI pages\");\n\n // 3. Scaffold API Routes\n const apiCmsDir = path.join(appDir, \"api\", \"cms\");\n\n // Login\n await fs.mkdir(path.join(apiCmsDir, \"login\"), { recursive: true });\n await fs.writeFile(\n path.join(apiCmsDir, \"login\", \"route.ts\"),\n `import { handleLogin } from '@fydemy/cms';\\nexport { handleLogin as POST };\\n`,\n \"utf-8\"\n );\n\n // Logout\n await fs.mkdir(path.join(apiCmsDir, \"logout\"), { recursive: true });\n await fs.writeFile(\n path.join(apiCmsDir, \"logout\", \"route.ts\"),\n `import { handleLogout } from '@fydemy/cms';\\nexport { handleLogout as POST };\\n`,\n \"utf-8\"\n );\n\n // Upload\n await fs.mkdir(path.join(apiCmsDir, \"upload\"), { recursive: true });\n await fs.writeFile(\n path.join(apiCmsDir, \"upload\", \"route.ts\"),\n `import { handleUpload } from '@fydemy/cms';\\nexport { handleUpload as POST };\\n`,\n \"utf-8\"\n );\n\n // List\n await fs.mkdir(path.join(apiCmsDir, \"list\", \"[[...path]]\"), {\n recursive: true,\n });\n await fs.writeFile(\n path.join(apiCmsDir, \"list\", \"[[...path]]\", \"route.ts\"),\n `import { createListApiHandlers } from '@fydemy/cms';\\n\\nconst handlers = createListApiHandlers();\\nexport const GET = handlers.GET;\\n`,\n \"utf-8\"\n );\n\n // Content\n await fs.mkdir(path.join(apiCmsDir, \"content\", \"[...path]\"), {\n recursive: true,\n });\n await fs.writeFile(\n path.join(apiCmsDir, \"content\", \"[...path]\", \"route.ts\"),\n `import { createContentApiHandlers } from '@fydemy/cms';\\n\\nconst handlers = createContentApiHandlers();\\nexport const GET = handlers.GET;\\nexport const POST = handlers.POST;\\nexport const DELETE = handlers.DELETE;\\n`,\n \"utf-8\"\n );\n console.log(\"✅ Created API routes\");\n\n // 4. Middleware\n const middlewarePath = path.join(process.cwd(), \"middleware.ts\");\n try {\n await fs.access(middlewarePath);\n console.log(\n \"⚠️ middleware.ts already exists. Please manually add the CMS auth middleware:\"\n );\n console.log(`\nimport { createAuthMiddleware } from '@fydemy/cms';\n// ... existing imports\n\nexport function middleware(request: NextRequest) {\n // Add this:\n const authResponse = createAuthMiddleware()(request);\n if (authResponse) return authResponse;\n \n // ... existing middleware logic\n}\n`);\n } catch {\n await fs.writeFile(\n middlewarePath,\n `import { createAuthMiddleware } from '@fydemy/cms';\\nimport { NextRequest } from 'next/server';\\n\\nexport function middleware(request: NextRequest) {\\n return createAuthMiddleware()(request);\\n}\\n\\nexport const config = {\\n matcher: ['/admin/:path*'],\\n};\\n`,\n \"utf-8\"\n );\n console.log(\"✅ Created middleware.ts\");\n }\n\n // 5. Env example\n const envExamplePath = path.join(process.cwd(), \".env.local.example\");\n await fs.writeFile(\n envExamplePath,\n `CMS_ADMIN_USERNAME=admin\\nCMS_ADMIN_PASSWORD=password\\nCMS_SESSION_SECRET=ensure_this_is_at_least_32_chars_long_random_string\\n\\n# GitHub Storage (Production)\\nGITHUB_TOKEN=\\nGITHUB_REPO=owner/repo\\nGITHUB_BRANCH=main\\n`,\n \"utf-8\"\n );\n\n console.log(\"\");\n console.log(\"🎉 CMS initialized successfully!\");\n console.log(\n \"1. Copy .env.local.example to .env.local and set your credentials\"\n );\n console.log(\"2. Run your dev server and visit /admin\");\n}\n","#!/usr/bin/env node\n\nimport { initCMS } from \"./init/setup\";\n\ninitCMS().catch((err: unknown) => {\n console.error(\"Error initializing CMS:\", err);\n process.exit(1);\n});\n"],"mappings":"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","names":["path","fs"]}
|