@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 ADDED
@@ -0,0 +1,310 @@
1
+ # @fydemy/cms
2
+
3
+ [![npm version](https://badge.fury.io/js/@fydemy%2Fcms.svg)](https://www.npmjs.com/package/@fydemy/cms)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)
6
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18.0.0-brightgreen.svg)](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
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,sBAAe;AACf,kBAAiB;AAYjB,eAAsB,QAAQ,SAAwB,CAAC,GAAG;AACxD,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,WAAW,YAAAA,QAAK,KAAK,QAAQ,IAAI,GAAG,UAAU;AACpD,QAAM,SAAS,YAAAA,QAAK,KAAK,QAAQ,IAAI,GAAG,KAAK;AAG7C,MAAI;AACF,UAAM,gBAAAC,QAAG,OAAO,MAAM;AAAA,EACxB,QAAQ;AACN,YAAQ;AAAA,MACN;AAAA,IACF;AACA;AAAA,EACF;AAEA,UAAQ,IAAI,uCAAgC;AAG5C,QAAM,gBAAAA,QAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE5C,QAAM,iBAAiB;AAAA;AAAA;AAAA,SAGjB,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgB9B,QAAM,cAAc,YAAAD,QAAK,KAAK,UAAU,YAAY;AACpD,QAAM,gBAAAC,QAAG,UAAU,aAAa,gBAAgB,OAAO;AACvD,UAAQ,IAAI,mDAA8C;AAG1D,QAAM,WAAW,YAAAD,QAAK,KAAK,QAAQ,OAAO;AAC1C,QAAM,WAAW,YAAAA,QAAK,KAAK,UAAU,OAAO;AAE5C,QAAM,gBAAAC,QAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE5C,QAAM,gBAAAA,QAAG;AAAA,IACP,YAAAD,QAAK,KAAK,UAAU,UAAU;AAAA,IAC9B;AAAA;AAAA;AAAA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,gBAAAC,QAAG;AAAA,IACP,YAAAD,QAAK,KAAK,UAAU,UAAU;AAAA,IAC9B;AAAA;AAAA;AAAA;AAAA,IACA;AAAA,EACF;AACA,UAAQ,IAAI,+BAA0B;AAGtC,QAAM,YAAY,YAAAA,QAAK,KAAK,QAAQ,OAAO,KAAK;AAGhD,QAAM,gBAAAC,QAAG,MAAM,YAAAD,QAAK,KAAK,WAAW,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AACjE,QAAM,gBAAAC,QAAG;AAAA,IACP,YAAAD,QAAK,KAAK,WAAW,SAAS,UAAU;AAAA,IACxC;AAAA;AAAA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,gBAAAC,QAAG,MAAM,YAAAD,QAAK,KAAK,WAAW,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAClE,QAAM,gBAAAC,QAAG;AAAA,IACP,YAAAD,QAAK,KAAK,WAAW,UAAU,UAAU;AAAA,IACzC;AAAA;AAAA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,gBAAAC,QAAG,MAAM,YAAAD,QAAK,KAAK,WAAW,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAClE,QAAM,gBAAAC,QAAG;AAAA,IACP,YAAAD,QAAK,KAAK,WAAW,UAAU,UAAU;AAAA,IACzC;AAAA;AAAA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,gBAAAC,QAAG,MAAM,YAAAD,QAAK,KAAK,WAAW,QAAQ,aAAa,GAAG;AAAA,IAC1D,WAAW;AAAA,EACb,CAAC;AACD,QAAM,gBAAAC,QAAG;AAAA,IACP,YAAAD,QAAK,KAAK,WAAW,QAAQ,eAAe,UAAU;AAAA,IACtD;AAAA;AAAA;AAAA;AAAA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,gBAAAC,QAAG,MAAM,YAAAD,QAAK,KAAK,WAAW,WAAW,WAAW,GAAG;AAAA,IAC3D,WAAW;AAAA,EACb,CAAC;AACD,QAAM,gBAAAC,QAAG;AAAA,IACP,YAAAD,QAAK,KAAK,WAAW,WAAW,aAAa,UAAU;AAAA,IACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IACA;AAAA,EACF;AACA,UAAQ,IAAI,2BAAsB;AAGlC,QAAM,iBAAiB,YAAAA,QAAK,KAAK,QAAQ,IAAI,GAAG,eAAe;AAC/D,MAAI;AACF,UAAM,gBAAAC,QAAG,OAAO,cAAc;AAC9B,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAWf;AAAA,EACC,QAAQ;AACN,UAAM,gBAAAA,QAAG;AAAA,MACP;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MACA;AAAA,IACF;AACA,YAAQ,IAAI,8BAAyB;AAAA,EACvC;AAGA,QAAM,iBAAiB,YAAAD,QAAK,KAAK,QAAQ,IAAI,GAAG,oBAAoB;AACpE,QAAM,gBAAAC,QAAG;AAAA,IACP;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IACA;AAAA,EACF;AAEA,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,yCAAkC;AAC9C,UAAQ;AAAA,IACN;AAAA,EACF;AACA,UAAQ,IAAI,yCAAyC;AACvD;;;ACjKA,QAAQ,EAAE,MAAM,CAAC,QAAiB;AAChC,UAAQ,MAAM,2BAA2B,GAAG;AAC5C,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["path","fs"]}