@fydemy/cms 1.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/CHANGELOG.md +50 -0
- package/LICENSE +21 -0
- package/dist/index.d.mts +431 -0
- package/dist/index.d.ts +431 -0
- package/dist/index.js +934 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +862 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface MarkdownData {
|
|
4
|
+
content: string;
|
|
5
|
+
data: Record<string, any>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parse markdown content with frontmatter
|
|
9
|
+
* @param rawContent - Raw markdown content to parse
|
|
10
|
+
* @returns Parsed markdown data with frontmatter and content
|
|
11
|
+
*/
|
|
12
|
+
declare function parseMarkdown(rawContent: string): MarkdownData;
|
|
13
|
+
/**
|
|
14
|
+
* Convert data and content back to markdown with frontmatter
|
|
15
|
+
*/
|
|
16
|
+
declare function stringifyMarkdown(data: Record<string, any>, content: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Read and parse a markdown file
|
|
19
|
+
* @param filePath - Path to the markdown file
|
|
20
|
+
* @returns Parsed markdown data
|
|
21
|
+
* @throws Error if file path is invalid or file is too large
|
|
22
|
+
*/
|
|
23
|
+
declare function getMarkdownContent(filePath: string): Promise<MarkdownData>;
|
|
24
|
+
/**
|
|
25
|
+
* Write markdown file with frontmatter
|
|
26
|
+
* @param filePath - Path to the markdown file
|
|
27
|
+
* @param data - Frontmatter data object
|
|
28
|
+
* @param content - Markdown content
|
|
29
|
+
* @throws Error if file path is invalid or content is too large
|
|
30
|
+
*/
|
|
31
|
+
declare function saveMarkdownContent(filePath: string, data: Record<string, any>, content: string): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Delete a markdown file
|
|
34
|
+
* @param filePath - Path to the markdown file
|
|
35
|
+
* @throws Error if file path is invalid
|
|
36
|
+
*/
|
|
37
|
+
declare function deleteMarkdownContent(filePath: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* List all markdown files in a directory
|
|
40
|
+
* @param directory - Directory path (optional, defaults to root)
|
|
41
|
+
* @returns Array of file paths
|
|
42
|
+
* @throws Error if directory path is invalid
|
|
43
|
+
*/
|
|
44
|
+
declare function listMarkdownFiles(directory?: string): Promise<string[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Check if a markdown file exists
|
|
47
|
+
* @param filePath - Path to the markdown file
|
|
48
|
+
* @returns True if file exists, false otherwise
|
|
49
|
+
* @throws Error if file path is invalid
|
|
50
|
+
*/
|
|
51
|
+
declare function markdownFileExists(filePath: string): Promise<boolean>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Represents a file or directory entry
|
|
55
|
+
*/
|
|
56
|
+
interface FileEntry {
|
|
57
|
+
/** Relative path from base content directory */
|
|
58
|
+
path: string;
|
|
59
|
+
/** File or directory name */
|
|
60
|
+
name: string;
|
|
61
|
+
/** Type of entry */
|
|
62
|
+
type: "file" | "directory";
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Interface for file storage operations
|
|
66
|
+
*/
|
|
67
|
+
interface StorageProvider {
|
|
68
|
+
/** Read file content as string */
|
|
69
|
+
readFile(filePath: string): Promise<string>;
|
|
70
|
+
/** Write content to file, creating parent directories if needed */
|
|
71
|
+
writeFile(filePath: string, content: string): Promise<void>;
|
|
72
|
+
/** Delete a file */
|
|
73
|
+
deleteFile(filePath: string): Promise<void>;
|
|
74
|
+
/** List files in a directory */
|
|
75
|
+
listFiles(directory: string): Promise<FileEntry[]>;
|
|
76
|
+
/** Check if a file exists */
|
|
77
|
+
exists(filePath: string): Promise<boolean>;
|
|
78
|
+
/** Upload a binary file */
|
|
79
|
+
uploadFile(filePath: string, buffer: Buffer): Promise<string>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Local file system storage (for development)
|
|
83
|
+
*/
|
|
84
|
+
declare class LocalStorage implements StorageProvider {
|
|
85
|
+
private baseDir;
|
|
86
|
+
constructor(baseDir?: string);
|
|
87
|
+
private getFullPath;
|
|
88
|
+
readFile(filePath: string): Promise<string>;
|
|
89
|
+
writeFile(filePath: string, content: string): Promise<void>;
|
|
90
|
+
deleteFile(filePath: string): Promise<void>;
|
|
91
|
+
listFiles(directory: string): Promise<FileEntry[]>;
|
|
92
|
+
exists(filePath: string): Promise<boolean>;
|
|
93
|
+
uploadFile(filePath: string, buffer: Buffer): Promise<string>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* GitHub storage (for production)
|
|
97
|
+
*/
|
|
98
|
+
declare class GitHubStorage implements StorageProvider {
|
|
99
|
+
private octokit;
|
|
100
|
+
private owner;
|
|
101
|
+
private repo;
|
|
102
|
+
private branch;
|
|
103
|
+
private baseDir;
|
|
104
|
+
constructor();
|
|
105
|
+
private getGitHubPath;
|
|
106
|
+
readFile(filePath: string): Promise<string>;
|
|
107
|
+
writeFile(filePath: string, content: string): Promise<void>;
|
|
108
|
+
deleteFile(filePath: string): Promise<void>;
|
|
109
|
+
listFiles(directory: string): Promise<FileEntry[]>;
|
|
110
|
+
exists(filePath: string): Promise<boolean>;
|
|
111
|
+
uploadFile(filePath: string, buffer: Buffer): Promise<string>;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get the appropriate storage provider based on environment
|
|
115
|
+
*/
|
|
116
|
+
declare function getStorageProvider(): StorageProvider;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* List all files and directories in a directory
|
|
120
|
+
* @param directory - Relative path to the directory (default: root)
|
|
121
|
+
* @returns Array of file entries (files and directories)
|
|
122
|
+
*/
|
|
123
|
+
declare function listDirectory(directory?: string): Promise<FileEntry[]>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Base interface for collection items with flexible frontmatter
|
|
127
|
+
*/
|
|
128
|
+
interface CollectionItem<T = Record<string, any>> {
|
|
129
|
+
/** The markdown content body */
|
|
130
|
+
content: string;
|
|
131
|
+
/** Frontmatter data with dynamic fields */
|
|
132
|
+
data: T;
|
|
133
|
+
/** File slug (filename without extension) */
|
|
134
|
+
slug: string;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Fetch all markdown files from a specific folder/collection
|
|
138
|
+
* @param folderName - The folder name under public/content (e.g., "blog", "pages")
|
|
139
|
+
* @returns Array of collection items with parsed frontmatter and content
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* // Fetch all blog posts
|
|
144
|
+
* const posts = await getCollectionItems("blog");
|
|
145
|
+
* posts.forEach(post => {
|
|
146
|
+
* console.log(post.data.title); // Access any frontmatter field
|
|
147
|
+
* console.log(post.slug); // Filename without .md
|
|
148
|
+
* });
|
|
149
|
+
*
|
|
150
|
+
* // With type inference for known fields
|
|
151
|
+
* interface BlogPost {
|
|
152
|
+
* title: string;
|
|
153
|
+
* date: string;
|
|
154
|
+
* author?: string;
|
|
155
|
+
* [key: string]: any; // Allow any other fields
|
|
156
|
+
* }
|
|
157
|
+
* const typedPosts = await getCollectionItems<BlogPost>("blog");
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
declare function getCollectionItems<T = Record<string, any>>(folderName: string): Promise<CollectionItem<T>[]>;
|
|
161
|
+
/**
|
|
162
|
+
* Fetch a single item from a collection by slug
|
|
163
|
+
* @param folderName - The folder name under public/content
|
|
164
|
+
* @param slug - The filename without .md extension
|
|
165
|
+
* @returns Single collection item or null if not found
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* // Fetch a specific blog post
|
|
170
|
+
* const post = await getCollectionItem("blog", "my-first-post");
|
|
171
|
+
* if (post) {
|
|
172
|
+
* console.log(post.data.title);
|
|
173
|
+
* }
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
declare function getCollectionItem<T = Record<string, any>>(folderName: string, slug: string): Promise<CollectionItem<T> | null>;
|
|
177
|
+
/**
|
|
178
|
+
* Get all unique folder names (collections) in the content directory
|
|
179
|
+
* @param baseDir - Base directory to scan (default: "")
|
|
180
|
+
* @returns Array of folder names
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const collections = await getCollections();
|
|
185
|
+
* // Returns: ["blog", "pages", "docs", ...]
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
declare function getCollections(baseDir?: string): Promise<string[]>;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Upload a file and return its public URL
|
|
192
|
+
* @param fileName - Original filename
|
|
193
|
+
* @param buffer - File content buffer
|
|
194
|
+
* @returns Public URL path to the uploaded file
|
|
195
|
+
*/
|
|
196
|
+
declare function uploadFile(fileName: string, buffer: Buffer): Promise<string>;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Session payload structure for JWT
|
|
200
|
+
*/
|
|
201
|
+
interface SessionPayload {
|
|
202
|
+
/** Username of the authenticated user */
|
|
203
|
+
username: string;
|
|
204
|
+
/** Expiration timestamp (Unix time) */
|
|
205
|
+
exp: number;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Create a new session token for the given username
|
|
209
|
+
* @param username - The username to create a session for
|
|
210
|
+
* @returns Signed JWT string
|
|
211
|
+
*/
|
|
212
|
+
declare function createSession(username: string): Promise<string>;
|
|
213
|
+
/**
|
|
214
|
+
* Verify and decode a session token
|
|
215
|
+
* @param token - The JWT token to verify
|
|
216
|
+
* @returns Decoded payload if valid, null otherwise
|
|
217
|
+
*/
|
|
218
|
+
declare function verifySession(token: string): Promise<SessionPayload | null>;
|
|
219
|
+
/**
|
|
220
|
+
* Get session from Next.js request cookies
|
|
221
|
+
*/
|
|
222
|
+
declare function getSessionFromCookies(): Promise<SessionPayload | null>;
|
|
223
|
+
/**
|
|
224
|
+
* Set session cookie
|
|
225
|
+
*/
|
|
226
|
+
declare function setSessionCookie(token: string): Promise<void>;
|
|
227
|
+
/**
|
|
228
|
+
* Clear session cookie
|
|
229
|
+
*/
|
|
230
|
+
declare function clearSessionCookie(): Promise<void>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Validate username and password against environment variables using timing-safe comparison
|
|
234
|
+
* @param username - Username to validate
|
|
235
|
+
* @param password - Password to validate
|
|
236
|
+
* @returns True if credentials are valid, false otherwise
|
|
237
|
+
* @throws Error if environment variables are not configured
|
|
238
|
+
*/
|
|
239
|
+
declare function validateCredentials(username: string, password: string): boolean;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Login endpoint with rate limiting
|
|
243
|
+
*/
|
|
244
|
+
declare function handleLogin(request: NextRequest): Promise<NextResponse<{
|
|
245
|
+
error: string;
|
|
246
|
+
}> | NextResponse<{
|
|
247
|
+
success: boolean;
|
|
248
|
+
}>>;
|
|
249
|
+
/**
|
|
250
|
+
* Logout endpoint
|
|
251
|
+
*/
|
|
252
|
+
declare function handleLogout(): Promise<NextResponse<{
|
|
253
|
+
success: boolean;
|
|
254
|
+
}>>;
|
|
255
|
+
/**
|
|
256
|
+
* Get content endpoint
|
|
257
|
+
*/
|
|
258
|
+
declare function handleGetContent(_request: NextRequest, filePath: string): Promise<NextResponse<{
|
|
259
|
+
error: string;
|
|
260
|
+
}> | NextResponse<MarkdownData>>;
|
|
261
|
+
/**
|
|
262
|
+
* Save content endpoint
|
|
263
|
+
*/
|
|
264
|
+
declare function handleSaveContent(request: NextRequest, filePath: string): Promise<NextResponse<{
|
|
265
|
+
error: string;
|
|
266
|
+
}> | NextResponse<{
|
|
267
|
+
success: boolean;
|
|
268
|
+
}>>;
|
|
269
|
+
/**
|
|
270
|
+
* Delete content endpoint
|
|
271
|
+
*/
|
|
272
|
+
declare function handleDeleteContent(_request: NextRequest, filePath: string): Promise<NextResponse<{
|
|
273
|
+
error: string;
|
|
274
|
+
}> | NextResponse<{
|
|
275
|
+
success: boolean;
|
|
276
|
+
}>>;
|
|
277
|
+
/**
|
|
278
|
+
* List files endpoint
|
|
279
|
+
*/
|
|
280
|
+
declare function handleListFiles(directory?: string): Promise<NextResponse<{
|
|
281
|
+
error: string;
|
|
282
|
+
}> | NextResponse<{
|
|
283
|
+
entries: FileEntry[];
|
|
284
|
+
}>>;
|
|
285
|
+
/**
|
|
286
|
+
* Create API route handlers for Next.js App Router
|
|
287
|
+
*/
|
|
288
|
+
declare function createContentApiHandlers(): {
|
|
289
|
+
GET(request: NextRequest, { params }: {
|
|
290
|
+
params: {
|
|
291
|
+
path: string[];
|
|
292
|
+
};
|
|
293
|
+
}): Promise<NextResponse<{
|
|
294
|
+
error: string;
|
|
295
|
+
}> | NextResponse<MarkdownData>>;
|
|
296
|
+
POST(request: NextRequest, { params }: {
|
|
297
|
+
params: {
|
|
298
|
+
path: string[];
|
|
299
|
+
};
|
|
300
|
+
}): Promise<NextResponse<{
|
|
301
|
+
error: string;
|
|
302
|
+
}> | NextResponse<{
|
|
303
|
+
success: boolean;
|
|
304
|
+
}>>;
|
|
305
|
+
DELETE(request: NextRequest, { params }: {
|
|
306
|
+
params: {
|
|
307
|
+
path: string[];
|
|
308
|
+
};
|
|
309
|
+
}): Promise<NextResponse<{
|
|
310
|
+
error: string;
|
|
311
|
+
}> | NextResponse<{
|
|
312
|
+
success: boolean;
|
|
313
|
+
}>>;
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* Create list API handlers
|
|
317
|
+
*/
|
|
318
|
+
declare function createListApiHandlers(): {
|
|
319
|
+
GET(_request: NextRequest, { params }: {
|
|
320
|
+
params: {
|
|
321
|
+
path?: string[];
|
|
322
|
+
};
|
|
323
|
+
}): Promise<NextResponse<{
|
|
324
|
+
error: string;
|
|
325
|
+
}> | NextResponse<{
|
|
326
|
+
entries: FileEntry[];
|
|
327
|
+
}>>;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Handle file upload
|
|
332
|
+
*/
|
|
333
|
+
declare function handleUpload(request: NextRequest): Promise<NextResponse<{
|
|
334
|
+
error: string;
|
|
335
|
+
}> | NextResponse<{
|
|
336
|
+
success: boolean;
|
|
337
|
+
url: string;
|
|
338
|
+
filename: string;
|
|
339
|
+
}>>;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Create middleware to protect admin routes
|
|
343
|
+
*/
|
|
344
|
+
declare function createAuthMiddleware(options?: {
|
|
345
|
+
loginPath?: string;
|
|
346
|
+
protectedPaths?: string[];
|
|
347
|
+
}): (request: NextRequest) => Promise<NextResponse<unknown>>;
|
|
348
|
+
|
|
349
|
+
interface InitCMSConfig {
|
|
350
|
+
/** Directory where content is stored (default: "public/content") */
|
|
351
|
+
contentDir?: string;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Initialize CMS in a Next.js project
|
|
355
|
+
* Creates the content directory and an example markdown file.
|
|
356
|
+
* @param config - Configuration options
|
|
357
|
+
*/
|
|
358
|
+
declare function initCMS(config?: InitCMSConfig): Promise<void>;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Maximum username length (prevents DoS)
|
|
362
|
+
*/
|
|
363
|
+
declare const MAX_USERNAME_LENGTH = 100;
|
|
364
|
+
/**
|
|
365
|
+
* Maximum password length (prevents DoS)
|
|
366
|
+
*/
|
|
367
|
+
declare const MAX_PASSWORD_LENGTH = 1000;
|
|
368
|
+
/**
|
|
369
|
+
* Maximum file size in bytes (10MB)
|
|
370
|
+
*/
|
|
371
|
+
declare const MAX_FILE_SIZE: number;
|
|
372
|
+
/**
|
|
373
|
+
* Validate and sanitize file path to prevent directory traversal
|
|
374
|
+
* @param filePath - The file path to validate
|
|
375
|
+
* @returns Sanitized file path
|
|
376
|
+
* @throws Error if path is invalid or attempts directory traversal
|
|
377
|
+
*/
|
|
378
|
+
declare function validateFilePath(filePath: string): string;
|
|
379
|
+
/**
|
|
380
|
+
* Validate username input
|
|
381
|
+
* @param username - The username to validate
|
|
382
|
+
* @returns True if valid
|
|
383
|
+
* @throws Error if invalid
|
|
384
|
+
*/
|
|
385
|
+
declare function validateUsername(username: string): boolean;
|
|
386
|
+
/**
|
|
387
|
+
* Validate password input
|
|
388
|
+
* @param password - The password to validate
|
|
389
|
+
* @returns True if valid
|
|
390
|
+
* @throws Error if invalid
|
|
391
|
+
*/
|
|
392
|
+
declare function validatePassword(password: string): boolean;
|
|
393
|
+
/**
|
|
394
|
+
* Validate file size
|
|
395
|
+
* @param size - File size in bytes
|
|
396
|
+
* @returns True if valid
|
|
397
|
+
* @throws Error if too large
|
|
398
|
+
*/
|
|
399
|
+
declare function validateFileSize(size: number): boolean;
|
|
400
|
+
/**
|
|
401
|
+
* Sanitize frontmatter data to prevent injection
|
|
402
|
+
* @param data - The data object to sanitize
|
|
403
|
+
* @returns Sanitized data
|
|
404
|
+
*/
|
|
405
|
+
declare function sanitizeFrontmatter(data: Record<string, any>): Record<string, any>;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Simple in-memory rate limiter for login attempts
|
|
409
|
+
*/
|
|
410
|
+
/**
|
|
411
|
+
* Check if IP/identifier is rate limited
|
|
412
|
+
* @param identifier - IP address or other unique identifier
|
|
413
|
+
* @returns Object with isLimited and remaining attempts
|
|
414
|
+
*/
|
|
415
|
+
declare function checkRateLimit(identifier: string): {
|
|
416
|
+
isLimited: boolean;
|
|
417
|
+
remaining: number;
|
|
418
|
+
resetTime: number;
|
|
419
|
+
};
|
|
420
|
+
/**
|
|
421
|
+
* Increment rate limit counter for identifier
|
|
422
|
+
* @param identifier - IP address or other unique identifier
|
|
423
|
+
*/
|
|
424
|
+
declare function incrementRateLimit(identifier: string): void;
|
|
425
|
+
/**
|
|
426
|
+
* Reset rate limit for identifier (use after successful login)
|
|
427
|
+
* @param identifier - IP address or other unique identifier
|
|
428
|
+
*/
|
|
429
|
+
declare function resetRateLimit(identifier: string): void;
|
|
430
|
+
|
|
431
|
+
export { type CollectionItem, type FileEntry, GitHubStorage, type InitCMSConfig, LocalStorage, MAX_FILE_SIZE, MAX_PASSWORD_LENGTH, MAX_USERNAME_LENGTH, type MarkdownData, type StorageProvider, checkRateLimit, clearSessionCookie, createAuthMiddleware, createContentApiHandlers, createListApiHandlers, createSession, deleteMarkdownContent, getCollectionItem, getCollectionItems, getCollections, getMarkdownContent, getSessionFromCookies, getStorageProvider, handleDeleteContent, handleGetContent, handleListFiles, handleLogin, handleLogout, handleSaveContent, handleUpload, incrementRateLimit, initCMS, listDirectory, listMarkdownFiles, markdownFileExists, parseMarkdown, resetRateLimit, sanitizeFrontmatter, saveMarkdownContent, setSessionCookie, stringifyMarkdown, uploadFile, validateCredentials, validateFilePath, validateFileSize, validatePassword, validateUsername, verifySession };
|