@imjp/writenex-astro 0.1.0 → 1.3.6

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.
Files changed (100) hide show
  1. package/README.md +13 -13
  2. package/dist/{chunk-CF2XXJFF.js → chunk-4H63L4YO.js} +436 -436
  3. package/dist/chunk-4H63L4YO.js.map +1 -0
  4. package/dist/{chunk-AAOQHQPU.js → chunk-GYAFIVVI.js} +6 -6
  5. package/dist/chunk-GYAFIVVI.js.map +1 -0
  6. package/dist/{chunk-XNTQTTJU.js → chunk-JFQQJPDF.js} +2 -2
  7. package/dist/{chunk-XNTQTTJU.js.map → chunk-JFQQJPDF.js.map} +1 -1
  8. package/dist/{chunk-CYLDJ3HZ.js → chunk-JMNCPNQX.js} +4 -4
  9. package/dist/{chunk-CYLDJ3HZ.js.map → chunk-JMNCPNQX.js.map} +1 -1
  10. package/dist/{chunk-5PM6EQE5.js → chunk-N37EPLKG.js} +13 -5
  11. package/dist/chunk-N37EPLKG.js.map +1 -0
  12. package/dist/{chunk-7XU5X6CW.js → chunk-NSW7AIVF.js} +12 -12
  13. package/dist/chunk-NSW7AIVF.js.map +1 -0
  14. package/dist/{chunk-CRPZUUDU.js → chunk-YBCPOLMY.js} +1 -1
  15. package/dist/{chunk-CRPZUUDU.js.map → chunk-YBCPOLMY.js.map} +1 -1
  16. package/dist/client/index.css +1 -1
  17. package/dist/client/index.css.map +1 -1
  18. package/dist/client/index.d.ts +19 -0
  19. package/dist/client/index.js +159 -147
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/client/styles.css +2 -8
  22. package/dist/config/index.d.ts +2 -2
  23. package/dist/config/index.js +2 -2
  24. package/dist/{config-BmEdBDo_.d.ts → config-CliL0CoN.d.ts} +1 -1
  25. package/dist/{content-BWR52vD-.d.ts → content-TuL3GT66.d.ts} +1 -1
  26. package/dist/discovery/index.d.ts +2 -2
  27. package/dist/discovery/index.js +3 -3
  28. package/dist/filesystem/index.d.ts +703 -703
  29. package/dist/filesystem/index.js +4 -4
  30. package/dist/filesystem/index.js.map +1 -1
  31. package/dist/index.d.ts +4 -4
  32. package/dist/index.js +8 -8
  33. package/dist/index.js.map +1 -1
  34. package/dist/{loader-55LWCXHA.js → loader-53VVP2IN.js} +3 -3
  35. package/dist/schema-DDJyoVkj.d.ts +189 -0
  36. package/dist/server/index.d.ts +37 -37
  37. package/dist/server/index.js +5 -5
  38. package/package.json +17 -18
  39. package/src/client/App.tsx +18 -18
  40. package/src/client/components/ConfigPanel/ConfigPanel.tsx +14 -13
  41. package/src/client/components/CreateContentModal/CreateContentModal.tsx +1 -1
  42. package/src/client/components/Editor/Editor.tsx +27 -27
  43. package/src/client/components/Editor/ImageDialog.tsx +4 -3
  44. package/src/client/components/Editor/LinkDialog.tsx +7 -6
  45. package/src/client/components/Editor/index.ts +1 -1
  46. package/src/client/components/FrontmatterForm/FrontmatterForm.css +1 -1
  47. package/src/client/components/FrontmatterForm/FrontmatterForm.tsx +1 -1
  48. package/src/client/components/Header/Header.tsx +8 -8
  49. package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.tsx +1 -1
  50. package/src/client/components/LazyEditor.tsx +1 -1
  51. package/src/client/components/LiveRegion/index.ts +1 -1
  52. package/src/client/components/SearchReplace/SearchReplacePanel.tsx +5 -5
  53. package/src/client/components/SearchReplace/index.ts +1 -1
  54. package/src/client/components/SelectCollectionModal/SelectCollectionModal.tsx +2 -2
  55. package/src/client/components/Sidebar/Sidebar.tsx +6 -6
  56. package/src/client/components/SkipLink/index.ts +1 -1
  57. package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.tsx +1 -1
  58. package/src/client/components/VersionHistory/DiffViewer.tsx +18 -11
  59. package/src/client/components/VersionHistory/VersionActions.tsx +6 -6
  60. package/src/client/components/VersionHistory/VersionHistoryPanel.tsx +10 -10
  61. package/src/client/components/VersionHistory/index.ts +2 -2
  62. package/src/client/context/ApiContext.tsx +2 -2
  63. package/src/client/context/ThemeContext.tsx +2 -2
  64. package/src/client/hooks/useApi.ts +1 -1
  65. package/src/client/hooks/useFocusTrap.ts +1 -1
  66. package/src/client/hooks/useSearch.ts +1 -1
  67. package/src/client/hooks/useVersionHistory.ts +2 -2
  68. package/src/client/index.tsx +1 -1
  69. package/src/client/styles.css +2 -8
  70. package/src/config/defaults.ts +4 -4
  71. package/src/config/index.ts +14 -16
  72. package/src/config/loader.ts +24 -4
  73. package/src/config/schema.ts +8 -4
  74. package/src/core/index.ts +1 -1
  75. package/src/discovery/collections.ts +3 -3
  76. package/src/discovery/index.ts +9 -11
  77. package/src/discovery/patterns.ts +2 -2
  78. package/src/discovery/schema.ts +1 -1
  79. package/src/filesystem/images.ts +3 -3
  80. package/src/filesystem/index.ts +74 -79
  81. package/src/filesystem/reader.ts +5 -3
  82. package/src/filesystem/version-config.ts +10 -10
  83. package/src/filesystem/versions.ts +9 -9
  84. package/src/filesystem/watcher.ts +1 -1
  85. package/src/filesystem/writer.ts +6 -6
  86. package/src/global.d.ts +39 -0
  87. package/src/index.ts +10 -10
  88. package/src/integration.ts +6 -3
  89. package/src/server/assets.ts +3 -3
  90. package/src/server/cache.ts +1 -1
  91. package/src/server/index.ts +12 -15
  92. package/src/server/middleware.ts +3 -3
  93. package/src/server/routes.ts +28 -28
  94. package/src/types/index.ts +24 -28
  95. package/dist/chunk-5PM6EQE5.js.map +0 -1
  96. package/dist/chunk-7XU5X6CW.js.map +0 -1
  97. package/dist/chunk-AAOQHQPU.js.map +0 -1
  98. package/dist/chunk-CF2XXJFF.js.map +0 -1
  99. package/dist/loader-CrdnaAWR.d.ts +0 -327
  100. /package/dist/{loader-55LWCXHA.js.map → loader-53VVP2IN.js.map} +0 -0
@@ -1,7 +1,255 @@
1
- import { C as ContentItem, a as ContentSummary } from '../content-BWR52vD-.js';
2
- import { V as VersionHistoryConfig, d as SaveVersionOptions, e as VersionResult, f as VersionEntry, g as Version, R as RestoreVersionOptions, h as RestoreResult, i as VersionManifest, I as ImageConfig } from '../config-BmEdBDo_.js';
3
- import { C as ContentConflictError } from '../errors-C0iYiDTv.js';
1
+ import { I as ImageConfig, V as VersionHistoryConfig, d as VersionResult, e as Version, f as VersionEntry, R as RestoreVersionOptions, g as RestoreResult, h as SaveVersionOptions, i as VersionManifest } from '../config-CliL0CoN.js';
4
2
  import { I as ImageDiscoveryOptions, a as ImageDiscoveryResult, D as DiscoveredImage } from '../image-FP7w5ZIs.js';
3
+ import { C as ContentItem, a as ContentSummary } from '../content-TuL3GT66.js';
4
+ import { C as ContentConflictError } from '../errors-C0iYiDTv.js';
5
+
6
+ /**
7
+ * @fileoverview Image handling for content collections
8
+ *
9
+ * This module provides functions for uploading and managing images
10
+ * in content collections with support for different storage strategies.
11
+ *
12
+ * ## Strategies:
13
+ * - colocated: Images stored alongside content files
14
+ * - public: Images stored in public directory
15
+ * - custom: User-defined storage paths
16
+ *
17
+ * @module @writenex/astro/filesystem/images
18
+ */
19
+
20
+ /**
21
+ * Default image configuration
22
+ */
23
+ declare const DEFAULT_IMAGE_CONFIG: ImageConfig;
24
+ /**
25
+ * Result of image upload operation
26
+ */
27
+ interface ImageUploadResult {
28
+ success: boolean;
29
+ /** Markdown-compatible path for the image */
30
+ path?: string;
31
+ /** Public URL for the image */
32
+ url?: string;
33
+ /** Error message if failed */
34
+ error?: string;
35
+ }
36
+ /**
37
+ * Options for image upload
38
+ */
39
+ interface ImageUploadOptions {
40
+ /** Original filename */
41
+ filename: string;
42
+ /** Image binary data */
43
+ data: Buffer;
44
+ /** Collection name */
45
+ collection: string;
46
+ /** Content ID (slug) */
47
+ contentId: string;
48
+ /** Project root path */
49
+ projectRoot: string;
50
+ /** Image configuration */
51
+ config?: ImageConfig;
52
+ }
53
+ /**
54
+ * Validate image file
55
+ *
56
+ * @param filename - Original filename
57
+ * @returns True if valid image file
58
+ */
59
+ declare function isValidImageFile(filename: string): boolean;
60
+ /**
61
+ * Upload an image file
62
+ *
63
+ * @param options - Upload options
64
+ * @returns Upload result with paths
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const result = await uploadImage({
69
+ * filename: "hero.jpg",
70
+ * data: imageBuffer,
71
+ * collection: "blog",
72
+ * contentId: "my-post",
73
+ * projectRoot: "/path/to/project",
74
+ * });
75
+ *
76
+ * if (result.success) {
77
+ * console.log(result.path); // "./my-post/hero-abc123.jpg"
78
+ * }
79
+ * ```
80
+ */
81
+ declare function uploadImage(options: ImageUploadOptions): Promise<ImageUploadResult>;
82
+ /**
83
+ * Parse multipart form data for image upload
84
+ *
85
+ * Simple parser for multipart/form-data with single file upload.
86
+ * For production, consider using a proper multipart parser library.
87
+ *
88
+ * @param body - Raw request body
89
+ * @param contentType - Content-Type header
90
+ * @returns Parsed file data and fields
91
+ */
92
+ declare function parseMultipartFormData(body: Buffer, contentType: string): {
93
+ file?: {
94
+ filename: string;
95
+ data: Buffer;
96
+ contentType: string;
97
+ };
98
+ fields: Record<string, string>;
99
+ };
100
+ /**
101
+ * Content structure type detected from file path
102
+ */
103
+ type ContentStructure = "flat" | "folder-based" | "date-prefixed";
104
+ /**
105
+ * Result of content structure detection
106
+ */
107
+ interface ContentStructureResult {
108
+ /** Detected structure type */
109
+ structure: ContentStructure;
110
+ /** Path to the image folder (null if doesn't exist) */
111
+ imageFolderPath: string | null;
112
+ }
113
+ /**
114
+ * Detect content structure and get the image folder path
115
+ *
116
+ * Handles three content structures:
117
+ * - Flat file: `my-post.md` -> looks for `my-post/` sibling folder
118
+ * - Folder-based: `slug/index.md` -> uses `slug/` parent folder
119
+ * - Date-prefixed: `2024-01-15-my-post.md` -> looks for `2024-01-15-my-post/` sibling folder
120
+ *
121
+ * @param collectionPath - Absolute path to the collection directory
122
+ * @param contentId - Content ID (slug)
123
+ * @param contentFilePath - Absolute path to the content file
124
+ * @returns Path to the image folder, or null if no image folder exists
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * // Flat file structure
129
+ * const folder = getContentImageFolder(
130
+ * '/project/src/content/blog',
131
+ * 'my-post',
132
+ * '/project/src/content/blog/my-post.md'
133
+ * );
134
+ * // Returns: '/project/src/content/blog/my-post' (if exists)
135
+ *
136
+ * // Folder-based structure
137
+ * const folder = getContentImageFolder(
138
+ * '/project/src/content/blog',
139
+ * 'my-post',
140
+ * '/project/src/content/blog/my-post/index.md'
141
+ * );
142
+ * // Returns: '/project/src/content/blog/my-post'
143
+ * ```
144
+ */
145
+ declare function getContentImageFolder(collectionPath: string, contentId: string, contentFilePath: string): string | null;
146
+ /**
147
+ * Detect the content structure type from a content file path
148
+ *
149
+ * @param contentFilePath - Absolute path to the content file
150
+ * @returns The detected content structure type
151
+ */
152
+ declare function detectContentStructure(contentFilePath: string): ContentStructure;
153
+ /**
154
+ * Options for recursive directory scanning
155
+ */
156
+ interface ScanOptions {
157
+ /** Maximum recursion depth */
158
+ maxDepth: number;
159
+ /** Current recursion depth */
160
+ currentDepth: number;
161
+ /** Base path for calculating relative paths */
162
+ basePath: string;
163
+ }
164
+ /**
165
+ * Scan a directory recursively for image files
166
+ *
167
+ * Recursively scans the given directory for image files with supported extensions.
168
+ * Skips hidden folders (starting with .) and special folders (starting with _).
169
+ * Limits recursion to the specified maxDepth.
170
+ *
171
+ * @param dirPath - Absolute path to the directory to scan
172
+ * @param basePath - Base path for calculating relative paths
173
+ * @param options - Scan options including maxDepth and currentDepth
174
+ * @returns Array of discovered images
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * const images = await scanDirectoryForImages(
179
+ * '/project/src/content/blog/my-post',
180
+ * '/project/src/content/blog/my-post',
181
+ * { maxDepth: 5, currentDepth: 0, basePath: '/project/src/content/blog/my-post' }
182
+ * );
183
+ * ```
184
+ */
185
+ declare function scanDirectoryForImages(dirPath: string, basePath: string, options: ScanOptions): Promise<DiscoveredImage[]>;
186
+ /**
187
+ * Calculate relative path from content file to image file
188
+ *
189
+ * Calculates the path that can be used in markdown to reference an image
190
+ * relative to the content file's location. The path always starts with ./
191
+ * to ensure it's treated as a relative reference.
192
+ *
193
+ * @param contentFilePath - Absolute path to the content file (e.g., /project/src/content/blog/my-post.md)
194
+ * @param imagePath - Absolute path to the image file (e.g., /project/src/content/blog/my-post/images/hero.jpg)
195
+ * @returns Relative path starting with ./ (e.g., ./my-post/images/hero.jpg)
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * // Flat file structure
200
+ * const relPath = calculateRelativePath(
201
+ * '/project/src/content/blog/my-post.md',
202
+ * '/project/src/content/blog/my-post/hero.jpg'
203
+ * );
204
+ * // Returns: './my-post/hero.jpg'
205
+ *
206
+ * // Folder-based structure
207
+ * const relPath = calculateRelativePath(
208
+ * '/project/src/content/blog/my-post/index.md',
209
+ * '/project/src/content/blog/my-post/images/hero.jpg'
210
+ * );
211
+ * // Returns: './images/hero.jpg'
212
+ *
213
+ * // Nested subfolder
214
+ * const relPath = calculateRelativePath(
215
+ * '/project/src/content/blog/my-post/index.md',
216
+ * '/project/src/content/blog/my-post/assets/photos/hero.jpg'
217
+ * );
218
+ * // Returns: './assets/photos/hero.jpg'
219
+ * ```
220
+ */
221
+ declare function calculateRelativePath(contentFilePath: string, imagePath: string): string;
222
+ /**
223
+ * Discover all images associated with a content item
224
+ *
225
+ * This is the main entry point for image discovery. It:
226
+ * 1. Locates the content file using getContentFilePath
227
+ * 2. Determines the image folder using getContentImageFolder
228
+ * 3. Scans the folder recursively using scanDirectoryForImages
229
+ * 4. Calculates relative paths for all discovered images
230
+ *
231
+ * @param collectionPath - Absolute path to the collection directory
232
+ * @param contentId - Content ID (slug)
233
+ * @param options - Optional discovery options
234
+ * @returns ImageDiscoveryResult with discovered images or error
235
+ *
236
+ * @example
237
+ * ```typescript
238
+ * const result = await discoverContentImages(
239
+ * '/project/src/content/blog',
240
+ * 'my-post',
241
+ * { maxDepth: 3 }
242
+ * );
243
+ *
244
+ * if (result.success) {
245
+ * console.log(`Found ${result.images.length} images`);
246
+ * for (const img of result.images) {
247
+ * console.log(`- ${img.relativePath}`);
248
+ * }
249
+ * }
250
+ * ```
251
+ */
252
+ declare function discoverContentImages(collectionPath: string, contentId: string, options?: ImageDiscoveryOptions): Promise<ImageDiscoveryResult>;
5
253
 
6
254
  /**
7
255
  * @fileoverview Filesystem reader for content collections
@@ -171,281 +419,220 @@ declare function getFileStats(filePath: string): Promise<{
171
419
  declare function getContentFilePath(collectionPath: string, contentId: string): string | null;
172
420
 
173
421
  /**
174
- * @fileoverview Filesystem writer for content operations
175
- *
176
- * This module provides functions for creating, updating, and deleting
177
- * content files in Astro content collections.
422
+ * @fileoverview Config-aware wrappers for version history operations
178
423
  *
179
- * ## Features:
180
- * - Create new content files with frontmatter
181
- * - Update existing content files
182
- * - Delete content files
183
- * - Generate unique slugs to avoid collisions
184
- * - Support for different file patterns (flat, folder-based, date-prefixed)
185
- * - Automatic version history creation before updates
424
+ * This module provides wrapper functions that automatically apply configuration
425
+ * defaults and check the enabled flag before performing version operations.
426
+ * These wrappers simplify usage by accepting partial configuration and handling
427
+ * all the configuration resolution internally.
186
428
  *
187
- * @module @writenex/astro/filesystem/writer
429
+ * @module @writenex/astro/filesystem/version-config
430
+ * @see {@link saveVersion} - Core save function
431
+ * @see {@link getVersions} - Core list function
188
432
  */
189
433
 
190
434
  /**
191
- * Options for creating content
192
- */
193
- interface CreateContentOptions {
194
- /** Frontmatter data */
195
- frontmatter: Record<string, unknown>;
196
- /** Markdown body content */
197
- body: string;
198
- /** Custom slug (optional, generated from title if not provided) */
199
- slug?: string;
200
- /** File pattern for the collection (e.g., "{slug}/index.md", "{date}-{slug}.md") */
201
- filePattern?: string;
202
- /** Custom token values to override automatic resolution */
203
- customTokens?: Record<string, string>;
204
- }
205
- /**
206
- * Options for updating content
207
- */
208
- interface UpdateContentOptions {
209
- /** Updated frontmatter data */
210
- frontmatter?: Record<string, unknown>;
211
- /** Updated markdown body content */
212
- body?: string;
213
- /** Project root for version history (required for version creation) */
214
- projectRoot?: string;
215
- /** Collection name for version history */
216
- collection?: string;
217
- /** Version history configuration */
218
- versionHistoryConfig?: Required<VersionHistoryConfig>;
219
- /**
220
- * Expected modification time for conflict detection.
221
- * If provided and the file's mtime differs, the update will fail with a conflict error.
222
- */
223
- expectedMtime?: number;
224
- }
225
- /**
226
- * Result of a write operation
227
- */
228
- interface WriteResult {
229
- /** Whether the operation was successful */
230
- success: boolean;
231
- /** The content ID (slug) */
232
- id?: string;
233
- /** The file path */
234
- path?: string;
235
- /** Error message if failed */
236
- error?: string;
237
- /** New modification time after write (for conflict detection) */
238
- mtime?: number;
239
- /** Conflict error if update failed due to external modification */
240
- conflict?: ContentConflictError;
241
- }
242
- /**
243
- * Generate a URL-safe slug from a string
435
+ * Resolve version history configuration with defaults applied.
244
436
  *
245
- * @param text - Text to slugify
246
- * @returns URL-safe slug
437
+ * Takes a partial configuration and merges it with defaults to produce
438
+ * a complete configuration object.
439
+ *
440
+ * @param config - Partial version history configuration
441
+ * @returns Complete configuration with all defaults applied
442
+ *
443
+ * @example
444
+ * ```typescript
445
+ * const resolved = resolveVersionConfig({ maxVersions: 50 });
446
+ * // Returns: { enabled: true, maxVersions: 50, storagePath: '.writenex/versions' }
447
+ * ```
247
448
  */
248
- declare function generateSlug(text: string): string;
449
+ declare function resolveVersionConfig(config?: VersionHistoryConfig): Required<VersionHistoryConfig>;
249
450
  /**
250
- * Generate a unique slug that doesn't conflict with existing files
451
+ * Check if version history is enabled in the configuration.
251
452
  *
252
- * @param baseSlug - The base slug to start with
253
- * @param collectionPath - Path to the collection directory
254
- * @param filePattern - File pattern (default: "{slug}.md")
255
- * @returns A unique slug
453
+ * @param config - Version history configuration (partial or full)
454
+ * @returns True if version history is enabled
455
+ *
456
+ * @example
457
+ * ```typescript
458
+ * if (isVersionHistoryEnabled({ enabled: false })) {
459
+ * // This won't execute
460
+ * }
461
+ * ```
256
462
  */
257
- declare function generateUniqueSlug(baseSlug: string, collectionPath: string, filePattern?: string): Promise<string>;
463
+ declare function isVersionHistoryEnabled(config?: VersionHistoryConfig): boolean;
258
464
  /**
259
- * Create a new content file in a collection
260
- *
261
- * Supports various file patterns with automatic token resolution:
262
- * - `{slug}.md` - Simple flat structure (default)
263
- * - `{slug}/index.md` - Folder-based content
264
- * - `{date}-{slug}.md` - Date-prefixed naming
265
- * - `{year}/{slug}.md` - Year folder structure
266
- * - `{year}/{month}/{slug}.md` - Year/month folder structure
267
- * - `{year}/{month}/{day}/{slug}.md` - Full date folder structure
268
- * - `{lang}/{slug}.md` - Language-prefixed (i18n)
269
- * - `{category}/{slug}.md` - Category folder structure
270
- * - `{author}/{slug}.md` - Author folder structure
271
- * - Any custom pattern with tokens from frontmatter
465
+ * Save a version with automatic configuration resolution.
272
466
  *
273
- * Token resolution priority:
274
- * 1. Custom tokens (explicitly provided via customTokens)
275
- * 2. Known token resolvers (date, year, month, lang, category, etc.)
276
- * 3. Frontmatter values (for custom tokens)
277
- * 4. Default values
467
+ * This wrapper automatically applies configuration defaults and checks
468
+ * the enabled flag before delegating to the core saveVersion function.
278
469
  *
279
- * @param collectionPath - Absolute path to the collection directory
280
- * @param options - Content creation options
281
- * @returns WriteResult with success status and file info
470
+ * @param projectRoot - Absolute path to project root
471
+ * @param collection - Collection name
472
+ * @param contentId - Content item ID (slug)
473
+ * @param content - Full markdown content to save
474
+ * @param config - Partial version history configuration
475
+ * @param options - Save options
476
+ * @returns Result of the save operation
282
477
  *
283
478
  * @example
284
479
  * ```typescript
285
- * // Flat structure
286
- * const result = await createContent('/project/src/content/blog', {
287
- * frontmatter: { title: 'My New Post', pubDate: new Date() },
288
- * body: '# Hello World',
289
- * });
480
+ * // With partial config - defaults are applied automatically
481
+ * const result = await saveVersionWithConfig(
482
+ * '/project',
483
+ * 'blog',
484
+ * 'my-post',
485
+ * '---\ntitle: My Post\n---\n\nContent...',
486
+ * { maxVersions: 50 } // enabled and storagePath use defaults
487
+ * );
488
+ * ```
489
+ */
490
+ declare function saveVersionWithConfig(projectRoot: string, collection: string, contentId: string, content: string, config?: VersionHistoryConfig, options?: SaveVersionOptions): Promise<VersionResult>;
491
+ /**
492
+ * Get all versions with automatic configuration resolution.
290
493
  *
291
- * // Folder-based structure
292
- * const result = await createContent('/project/src/content/blog', {
293
- * frontmatter: { title: 'My New Post', pubDate: new Date() },
294
- * body: '# Hello World',
295
- * filePattern: '{slug}/index.md',
296
- * });
494
+ * This wrapper automatically applies configuration defaults and checks
495
+ * the enabled flag before delegating to the core getVersions function.
297
496
  *
298
- * // i18n structure with custom token
299
- * const result = await createContent('/project/src/content/blog', {
300
- * frontmatter: { title: 'My New Post', lang: 'id' },
301
- * body: '# Hello World',
302
- * filePattern: '{lang}/{slug}.md',
303
- * });
497
+ * @param projectRoot - Absolute path to project root
498
+ * @param collection - Collection name
499
+ * @param contentId - Content item ID (slug)
500
+ * @param config - Partial version history configuration
501
+ * @returns Array of version entries (empty if disabled)
304
502
  *
305
- * // Custom pattern with explicit token
306
- * const result = await createContent('/project/src/content/blog', {
307
- * frontmatter: { title: 'My New Post' },
308
- * body: '# Hello World',
309
- * filePattern: '{category}/{slug}.md',
310
- * customTokens: { category: 'tutorials' },
311
- * });
503
+ * @example
504
+ * ```typescript
505
+ * const versions = await getVersionsWithConfig(
506
+ * '/project',
507
+ * 'blog',
508
+ * 'my-post',
509
+ * { storagePath: 'custom/versions' }
510
+ * );
312
511
  * ```
313
512
  */
314
- declare function createContent(collectionPath: string, options: CreateContentOptions): Promise<WriteResult>;
513
+ declare function getVersionsWithConfig(projectRoot: string, collection: string, contentId: string, config?: VersionHistoryConfig): Promise<VersionEntry[]>;
315
514
  /**
316
- * Update an existing content file
515
+ * Get a specific version with automatic configuration resolution.
317
516
  *
318
- * Creates a version snapshot of the current content before updating
319
- * when version history is configured. Skips both version creation and
320
- * file write if the new content is identical to the current file content.
321
- * Version creation errors are logged but do not fail the save operation.
517
+ * This wrapper automatically applies configuration defaults and checks
518
+ * the enabled flag before delegating to the core getVersion function.
322
519
  *
323
- * @param filePath - Absolute path to the content file
324
- * @param collectionPath - Path to the collection directory
325
- * @param options - Update options including version history config
326
- * @returns WriteResult with success status
520
+ * @param projectRoot - Absolute path to project root
521
+ * @param collection - Collection name
522
+ * @param contentId - Content item ID (slug)
523
+ * @param versionId - Version ID to retrieve
524
+ * @param config - Partial version history configuration
525
+ * @returns Full version data or null if not found/disabled
327
526
  *
328
527
  * @example
329
528
  * ```typescript
330
- * const result = await updateContent(
331
- * '/project/src/content/blog/my-post.md',
332
- * '/project/src/content/blog',
333
- * {
334
- * frontmatter: { title: 'Updated Title' },
335
- * body: '# Updated Content',
336
- * projectRoot: '/project',
337
- * collection: 'blog',
338
- * versionHistoryConfig: { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' },
339
- * }
529
+ * const version = await getVersionWithConfig(
530
+ * '/project',
531
+ * 'blog',
532
+ * 'my-post',
533
+ * '2024-12-11T10-30-00-000Z'
340
534
  * );
341
535
  * ```
342
536
  */
343
- declare function updateContent(filePath: string, collectionPath: string, options: UpdateContentOptions): Promise<WriteResult>;
537
+ declare function getVersionWithConfig(projectRoot: string, collection: string, contentId: string, versionId: string, config?: VersionHistoryConfig): Promise<Version | null>;
344
538
  /**
345
- * Delete a content file
539
+ * Delete a version with automatic configuration resolution.
346
540
  *
347
- * @param filePath - Absolute path to the content file
348
- * @returns WriteResult with success status
541
+ * This wrapper automatically applies configuration defaults before
542
+ * delegating to the core deleteVersion function.
543
+ *
544
+ * @param projectRoot - Absolute path to project root
545
+ * @param collection - Collection name
546
+ * @param contentId - Content item ID (slug)
547
+ * @param versionId - Version ID to delete
548
+ * @param config - Partial version history configuration
549
+ * @returns Result of the delete operation
349
550
  *
350
551
  * @example
351
552
  * ```typescript
352
- * const result = await deleteContent('/project/src/content/blog/my-post.md');
553
+ * const result = await deleteVersionWithConfig(
554
+ * '/project',
555
+ * 'blog',
556
+ * 'my-post',
557
+ * '2024-12-11T10-30-00-000Z'
558
+ * );
353
559
  * ```
354
560
  */
355
- declare function deleteContent(filePath: string): Promise<WriteResult>;
356
-
561
+ declare function deleteVersionWithConfig(projectRoot: string, collection: string, contentId: string, versionId: string, config?: VersionHistoryConfig): Promise<VersionResult>;
357
562
  /**
358
- * @fileoverview File watcher for detecting external changes
563
+ * Clear all versions with automatic configuration resolution.
359
564
  *
360
- * This module provides file watching capabilities to detect when
361
- * content files are modified outside of the Writenex editor
362
- * (e.g., in VS Code or another editor).
565
+ * This wrapper automatically applies configuration defaults before
566
+ * delegating to the core clearVersions function.
363
567
  *
364
- * @module @writenex/astro/filesystem/watcher
365
- */
366
- /**
367
- * File change event types
368
- */
369
- type FileChangeType = "add" | "change" | "unlink";
370
- /**
371
- * File change event
372
- */
373
- interface FileChangeEvent {
374
- type: FileChangeType;
375
- path: string;
376
- collection: string;
377
- }
378
- /**
379
- * Watcher options
380
- */
381
- interface WatcherOptions {
382
- /** Callback when a file changes */
383
- onChange?: (event: FileChangeEvent) => void;
384
- /** Debounce delay in milliseconds */
385
- debounceMs?: number;
386
- /** Patterns to ignore */
387
- ignored?: string[];
388
- }
389
- /**
390
- * Content file watcher
568
+ * @param projectRoot - Absolute path to project root
569
+ * @param collection - Collection name
570
+ * @param contentId - Content item ID (slug)
571
+ * @param config - Partial version history configuration
572
+ * @returns Result of the clear operation
391
573
  *
392
- * Watches the src/content directory for changes and emits events
393
- * when files are added, modified, or deleted.
574
+ * @example
575
+ * ```typescript
576
+ * const result = await clearVersionsWithConfig(
577
+ * '/project',
578
+ * 'blog',
579
+ * 'my-post'
580
+ * );
581
+ * ```
394
582
  */
395
- declare class ContentWatcher {
396
- private watcher;
397
- private projectRoot;
398
- private contentDir;
399
- private options;
400
- private debounceTimers;
401
- constructor(projectRoot: string, contentDir?: string, options?: WatcherOptions);
402
- /**
403
- * Start watching for file changes
404
- */
405
- start(): void;
406
- /**
407
- * Stop watching for file changes
408
- */
409
- stop(): Promise<void>;
410
- /**
411
- * Handle a file change event
412
- */
413
- private handleChange;
414
- /**
415
- * Emit a file change event
416
- */
417
- private emitChange;
418
- /**
419
- * Check if the watcher is running
420
- */
421
- isWatching(): boolean;
422
- }
583
+ declare function clearVersionsWithConfig(projectRoot: string, collection: string, contentId: string, config?: VersionHistoryConfig): Promise<VersionResult>;
423
584
  /**
424
- * Track file modification times for conflict detection
585
+ * Prune old versions with automatic configuration resolution.
586
+ *
587
+ * This wrapper automatically applies configuration defaults before
588
+ * delegating to the core pruneVersions function. Uses the configured
589
+ * maxVersions value for determining how many versions to keep.
590
+ *
591
+ * @param projectRoot - Absolute path to project root
592
+ * @param collection - Collection name
593
+ * @param contentId - Content item ID (slug)
594
+ * @param config - Partial version history configuration
595
+ * @returns Result of the prune operation
596
+ *
597
+ * @example
598
+ * ```typescript
599
+ * // Uses custom maxVersions
600
+ * const result = await pruneVersionsWithConfig(
601
+ * '/project',
602
+ * 'blog',
603
+ * 'my-post',
604
+ * { maxVersions: 10 }
605
+ * );
606
+ * ```
425
607
  */
426
- declare class FileModificationTracker {
427
- private mtimes;
428
- /**
429
- * Record the current modification time of a file
430
- */
431
- track(filePath: string): Promise<void>;
432
- /**
433
- * Check if a file has been modified externally
434
- */
435
- hasExternalChanges(filePath: string): Promise<boolean>;
436
- /**
437
- * Clear tracking for a file
438
- */
439
- untrack(filePath: string): void;
440
- /**
441
- * Clear all tracking
442
- */
443
- clear(): void;
444
- }
608
+ declare function pruneVersionsWithConfig(projectRoot: string, collection: string, contentId: string, config?: VersionHistoryConfig): Promise<VersionResult>;
445
609
  /**
446
- * Create a content watcher instance
610
+ * Restore a version with automatic configuration resolution.
611
+ *
612
+ * This wrapper automatically applies configuration defaults and checks
613
+ * the enabled flag before delegating to the core restoreVersion function.
614
+ *
615
+ * @param projectRoot - Absolute path to project root
616
+ * @param collection - Collection name
617
+ * @param contentId - Content item ID (slug)
618
+ * @param versionId - Version ID to restore
619
+ * @param contentFilePath - Absolute path to the current content file
620
+ * @param config - Partial version history configuration
621
+ * @param options - Restore options
622
+ * @returns Result of the restore operation
623
+ *
624
+ * @example
625
+ * ```typescript
626
+ * const result = await restoreVersionWithConfig(
627
+ * '/project',
628
+ * 'blog',
629
+ * 'my-post',
630
+ * '2024-12-11T10-30-00-000Z',
631
+ * '/project/src/content/blog/my-post.md'
632
+ * );
633
+ * ```
447
634
  */
448
- declare function createContentWatcher(projectRoot: string, options?: WatcherOptions): ContentWatcher;
635
+ declare function restoreVersionWithConfig(projectRoot: string, collection: string, contentId: string, versionId: string, contentFilePath: string, config?: VersionHistoryConfig, options?: RestoreVersionOptions): Promise<RestoreResult>;
449
636
 
450
637
  /**
451
638
  * @fileoverview Version history management for content files
@@ -702,591 +889,404 @@ declare function getVersions(projectRoot: string, collection: string, contentId:
702
889
  /**
703
890
  * Get a specific version with full content.
704
891
  *
705
- * Reads the version file and parses it to return structured data
706
- * with frontmatter and body separated.
707
- *
708
- * @param projectRoot - Absolute path to project root
709
- * @param collection - Collection name
710
- * @param contentId - Content item ID (slug)
711
- * @param versionId - Version ID to retrieve
712
- * @param config - Version history configuration
713
- * @returns Full version data or null if not found
714
- *
715
- * @example
716
- * ```typescript
717
- * const version = await getVersion(
718
- * '/project',
719
- * 'blog',
720
- * 'my-post',
721
- * '2024-12-11T10-30-00-000Z',
722
- * { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' }
723
- * );
724
- *
725
- * if (version) {
726
- * console.log(`Title: ${version.frontmatter.title}`);
727
- * console.log(`Body: ${version.body}`);
728
- * }
729
- * ```
730
- */
731
- declare function getVersion(projectRoot: string, collection: string, contentId: string, versionId: string, config: Required<VersionHistoryConfig>): Promise<Version | null>;
732
- /**
733
- * Delete a specific version.
734
- *
735
- * Removes the version file from the filesystem and updates the manifest.
736
- *
737
- * @param projectRoot - Absolute path to project root
738
- * @param collection - Collection name
739
- * @param contentId - Content item ID (slug)
740
- * @param versionId - Version ID to delete
741
- * @param config - Version history configuration
742
- * @returns Result of the delete operation
743
- *
744
- * @example
745
- * ```typescript
746
- * const result = await deleteVersion(
747
- * '/project',
748
- * 'blog',
749
- * 'my-post',
750
- * '2024-12-11T10-30-00-000Z',
751
- * { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' }
752
- * );
753
- *
754
- * if (result.success) {
755
- * console.log('Version deleted');
756
- * }
757
- * ```
758
- */
759
- declare function deleteVersion(projectRoot: string, collection: string, contentId: string, versionId: string, config: Required<VersionHistoryConfig>): Promise<VersionResult>;
760
- /**
761
- * Clear all versions for a content item.
762
- *
763
- * Deletes all version files and resets the manifest to empty state.
764
- *
765
- * @param projectRoot - Absolute path to project root
766
- * @param collection - Collection name
767
- * @param contentId - Content item ID (slug)
768
- * @param config - Version history configuration
769
- * @returns Result of the clear operation
770
- *
771
- * @example
772
- * ```typescript
773
- * const result = await clearVersions(
774
- * '/project',
775
- * 'blog',
776
- * 'my-post',
777
- * { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' }
778
- * );
779
- *
780
- * if (result.success) {
781
- * console.log('All versions cleared');
782
- * }
783
- * ```
784
- */
785
- declare function clearVersions(projectRoot: string, collection: string, contentId: string, config: Required<VersionHistoryConfig>): Promise<VersionResult>;
786
- declare function pruneVersions(projectRoot: string, collection: string, contentId: string, config: Required<VersionHistoryConfig>): Promise<VersionResult>;
787
- /**
788
- * Restore a version to current content.
789
- *
790
- * This function:
791
- * 1. Creates a safety snapshot of the current content before restoring
792
- * 2. Reads the version content to restore
793
- * 3. Overwrites the current content file with the version content
794
- *
795
- * Note: Cache invalidation should be handled by the caller (API route)
796
- * since the cache is managed at the server level.
797
- *
798
- * @param projectRoot - Absolute path to project root
799
- * @param collection - Collection name
800
- * @param contentId - Content item ID (slug)
801
- * @param versionId - Version ID to restore
802
- * @param contentFilePath - Absolute path to the current content file
803
- * @param config - Version history configuration
804
- * @param options - Restore options
805
- * @returns Result of the restore operation
806
- *
807
- * @example
808
- * ```typescript
809
- * const result = await restoreVersion(
810
- * '/project',
811
- * 'blog',
812
- * 'my-post',
813
- * '2024-12-11T10-30-00-000Z',
814
- * '/project/src/content/blog/my-post.md',
815
- * { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' }
816
- * );
817
- *
818
- * if (result.success) {
819
- * console.log('Restored content:', result.content);
820
- * if (result.safetySnapshot) {
821
- * console.log('Safety snapshot created:', result.safetySnapshot.id);
822
- * }
823
- * }
824
- * ```
825
- */
826
- declare function restoreVersion(projectRoot: string, collection: string, contentId: string, versionId: string, contentFilePath: string, config: Required<VersionHistoryConfig>, options?: RestoreVersionOptions): Promise<RestoreResult>;
827
-
828
- /**
829
- * @fileoverview Config-aware wrappers for version history operations
830
- *
831
- * This module provides wrapper functions that automatically apply configuration
832
- * defaults and check the enabled flag before performing version operations.
833
- * These wrappers simplify usage by accepting partial configuration and handling
834
- * all the configuration resolution internally.
835
- *
836
- * @module @writenex/astro/filesystem/version-config
837
- * @see {@link saveVersion} - Core save function
838
- * @see {@link getVersions} - Core list function
839
- */
840
-
841
- /**
842
- * Resolve version history configuration with defaults applied.
843
- *
844
- * Takes a partial configuration and merges it with defaults to produce
845
- * a complete configuration object.
846
- *
847
- * @param config - Partial version history configuration
848
- * @returns Complete configuration with all defaults applied
849
- *
850
- * @example
851
- * ```typescript
852
- * const resolved = resolveVersionConfig({ maxVersions: 50 });
853
- * // Returns: { enabled: true, maxVersions: 50, storagePath: '.writenex/versions' }
854
- * ```
855
- */
856
- declare function resolveVersionConfig(config?: VersionHistoryConfig): Required<VersionHistoryConfig>;
857
- /**
858
- * Check if version history is enabled in the configuration.
859
- *
860
- * @param config - Version history configuration (partial or full)
861
- * @returns True if version history is enabled
862
- *
863
- * @example
864
- * ```typescript
865
- * if (isVersionHistoryEnabled({ enabled: false })) {
866
- * // This won't execute
867
- * }
868
- * ```
869
- */
870
- declare function isVersionHistoryEnabled(config?: VersionHistoryConfig): boolean;
871
- /**
872
- * Save a version with automatic configuration resolution.
873
- *
874
- * This wrapper automatically applies configuration defaults and checks
875
- * the enabled flag before delegating to the core saveVersion function.
876
- *
877
- * @param projectRoot - Absolute path to project root
878
- * @param collection - Collection name
879
- * @param contentId - Content item ID (slug)
880
- * @param content - Full markdown content to save
881
- * @param config - Partial version history configuration
882
- * @param options - Save options
883
- * @returns Result of the save operation
884
- *
885
- * @example
886
- * ```typescript
887
- * // With partial config - defaults are applied automatically
888
- * const result = await saveVersionWithConfig(
889
- * '/project',
890
- * 'blog',
891
- * 'my-post',
892
- * '---\ntitle: My Post\n---\n\nContent...',
893
- * { maxVersions: 50 } // enabled and storagePath use defaults
894
- * );
895
- * ```
896
- */
897
- declare function saveVersionWithConfig(projectRoot: string, collection: string, contentId: string, content: string, config?: VersionHistoryConfig, options?: SaveVersionOptions): Promise<VersionResult>;
898
- /**
899
- * Get all versions with automatic configuration resolution.
900
- *
901
- * This wrapper automatically applies configuration defaults and checks
902
- * the enabled flag before delegating to the core getVersions function.
903
- *
904
- * @param projectRoot - Absolute path to project root
905
- * @param collection - Collection name
906
- * @param contentId - Content item ID (slug)
907
- * @param config - Partial version history configuration
908
- * @returns Array of version entries (empty if disabled)
909
- *
910
- * @example
911
- * ```typescript
912
- * const versions = await getVersionsWithConfig(
913
- * '/project',
914
- * 'blog',
915
- * 'my-post',
916
- * { storagePath: 'custom/versions' }
917
- * );
918
- * ```
919
- */
920
- declare function getVersionsWithConfig(projectRoot: string, collection: string, contentId: string, config?: VersionHistoryConfig): Promise<VersionEntry[]>;
921
- /**
922
- * Get a specific version with automatic configuration resolution.
923
- *
924
- * This wrapper automatically applies configuration defaults and checks
925
- * the enabled flag before delegating to the core getVersion function.
926
- *
927
- * @param projectRoot - Absolute path to project root
928
- * @param collection - Collection name
929
- * @param contentId - Content item ID (slug)
930
- * @param versionId - Version ID to retrieve
931
- * @param config - Partial version history configuration
932
- * @returns Full version data or null if not found/disabled
933
- *
934
- * @example
935
- * ```typescript
936
- * const version = await getVersionWithConfig(
937
- * '/project',
938
- * 'blog',
939
- * 'my-post',
940
- * '2024-12-11T10-30-00-000Z'
941
- * );
942
- * ```
943
- */
944
- declare function getVersionWithConfig(projectRoot: string, collection: string, contentId: string, versionId: string, config?: VersionHistoryConfig): Promise<Version | null>;
945
- /**
946
- * Delete a version with automatic configuration resolution.
947
- *
948
- * This wrapper automatically applies configuration defaults before
949
- * delegating to the core deleteVersion function.
892
+ * Reads the version file and parses it to return structured data
893
+ * with frontmatter and body separated.
950
894
  *
951
895
  * @param projectRoot - Absolute path to project root
952
896
  * @param collection - Collection name
953
897
  * @param contentId - Content item ID (slug)
954
- * @param versionId - Version ID to delete
955
- * @param config - Partial version history configuration
956
- * @returns Result of the delete operation
898
+ * @param versionId - Version ID to retrieve
899
+ * @param config - Version history configuration
900
+ * @returns Full version data or null if not found
957
901
  *
958
902
  * @example
959
903
  * ```typescript
960
- * const result = await deleteVersionWithConfig(
904
+ * const version = await getVersion(
961
905
  * '/project',
962
906
  * 'blog',
963
907
  * 'my-post',
964
- * '2024-12-11T10-30-00-000Z'
908
+ * '2024-12-11T10-30-00-000Z',
909
+ * { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' }
965
910
  * );
911
+ *
912
+ * if (version) {
913
+ * console.log(`Title: ${version.frontmatter.title}`);
914
+ * console.log(`Body: ${version.body}`);
915
+ * }
966
916
  * ```
967
917
  */
968
- declare function deleteVersionWithConfig(projectRoot: string, collection: string, contentId: string, versionId: string, config?: VersionHistoryConfig): Promise<VersionResult>;
918
+ declare function getVersion(projectRoot: string, collection: string, contentId: string, versionId: string, config: Required<VersionHistoryConfig>): Promise<Version | null>;
969
919
  /**
970
- * Clear all versions with automatic configuration resolution.
920
+ * Delete a specific version.
971
921
  *
972
- * This wrapper automatically applies configuration defaults before
973
- * delegating to the core clearVersions function.
922
+ * Removes the version file from the filesystem and updates the manifest.
974
923
  *
975
924
  * @param projectRoot - Absolute path to project root
976
925
  * @param collection - Collection name
977
926
  * @param contentId - Content item ID (slug)
978
- * @param config - Partial version history configuration
979
- * @returns Result of the clear operation
927
+ * @param versionId - Version ID to delete
928
+ * @param config - Version history configuration
929
+ * @returns Result of the delete operation
980
930
  *
981
931
  * @example
982
932
  * ```typescript
983
- * const result = await clearVersionsWithConfig(
933
+ * const result = await deleteVersion(
984
934
  * '/project',
985
935
  * 'blog',
986
- * 'my-post'
936
+ * 'my-post',
937
+ * '2024-12-11T10-30-00-000Z',
938
+ * { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' }
987
939
  * );
940
+ *
941
+ * if (result.success) {
942
+ * console.log('Version deleted');
943
+ * }
988
944
  * ```
989
945
  */
990
- declare function clearVersionsWithConfig(projectRoot: string, collection: string, contentId: string, config?: VersionHistoryConfig): Promise<VersionResult>;
946
+ declare function deleteVersion(projectRoot: string, collection: string, contentId: string, versionId: string, config: Required<VersionHistoryConfig>): Promise<VersionResult>;
991
947
  /**
992
- * Prune old versions with automatic configuration resolution.
948
+ * Clear all versions for a content item.
993
949
  *
994
- * This wrapper automatically applies configuration defaults before
995
- * delegating to the core pruneVersions function. Uses the configured
996
- * maxVersions value for determining how many versions to keep.
950
+ * Deletes all version files and resets the manifest to empty state.
997
951
  *
998
952
  * @param projectRoot - Absolute path to project root
999
953
  * @param collection - Collection name
1000
954
  * @param contentId - Content item ID (slug)
1001
- * @param config - Partial version history configuration
1002
- * @returns Result of the prune operation
955
+ * @param config - Version history configuration
956
+ * @returns Result of the clear operation
1003
957
  *
1004
958
  * @example
1005
959
  * ```typescript
1006
- * // Uses custom maxVersions
1007
- * const result = await pruneVersionsWithConfig(
960
+ * const result = await clearVersions(
1008
961
  * '/project',
1009
962
  * 'blog',
1010
963
  * 'my-post',
1011
- * { maxVersions: 10 }
964
+ * { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' }
1012
965
  * );
966
+ *
967
+ * if (result.success) {
968
+ * console.log('All versions cleared');
969
+ * }
1013
970
  * ```
1014
971
  */
1015
- declare function pruneVersionsWithConfig(projectRoot: string, collection: string, contentId: string, config?: VersionHistoryConfig): Promise<VersionResult>;
972
+ declare function clearVersions(projectRoot: string, collection: string, contentId: string, config: Required<VersionHistoryConfig>): Promise<VersionResult>;
973
+ declare function pruneVersions(projectRoot: string, collection: string, contentId: string, config: Required<VersionHistoryConfig>): Promise<VersionResult>;
1016
974
  /**
1017
- * Restore a version with automatic configuration resolution.
975
+ * Restore a version to current content.
1018
976
  *
1019
- * This wrapper automatically applies configuration defaults and checks
1020
- * the enabled flag before delegating to the core restoreVersion function.
977
+ * This function:
978
+ * 1. Creates a safety snapshot of the current content before restoring
979
+ * 2. Reads the version content to restore
980
+ * 3. Overwrites the current content file with the version content
981
+ *
982
+ * Note: Cache invalidation should be handled by the caller (API route)
983
+ * since the cache is managed at the server level.
1021
984
  *
1022
985
  * @param projectRoot - Absolute path to project root
1023
986
  * @param collection - Collection name
1024
987
  * @param contentId - Content item ID (slug)
1025
988
  * @param versionId - Version ID to restore
1026
989
  * @param contentFilePath - Absolute path to the current content file
1027
- * @param config - Partial version history configuration
990
+ * @param config - Version history configuration
1028
991
  * @param options - Restore options
1029
992
  * @returns Result of the restore operation
1030
993
  *
1031
994
  * @example
1032
995
  * ```typescript
1033
- * const result = await restoreVersionWithConfig(
996
+ * const result = await restoreVersion(
1034
997
  * '/project',
1035
998
  * 'blog',
1036
999
  * 'my-post',
1037
1000
  * '2024-12-11T10-30-00-000Z',
1038
- * '/project/src/content/blog/my-post.md'
1001
+ * '/project/src/content/blog/my-post.md',
1002
+ * { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' }
1039
1003
  * );
1004
+ *
1005
+ * if (result.success) {
1006
+ * console.log('Restored content:', result.content);
1007
+ * if (result.safetySnapshot) {
1008
+ * console.log('Safety snapshot created:', result.safetySnapshot.id);
1009
+ * }
1010
+ * }
1040
1011
  * ```
1041
1012
  */
1042
- declare function restoreVersionWithConfig(projectRoot: string, collection: string, contentId: string, versionId: string, contentFilePath: string, config?: VersionHistoryConfig, options?: RestoreVersionOptions): Promise<RestoreResult>;
1013
+ declare function restoreVersion(projectRoot: string, collection: string, contentId: string, versionId: string, contentFilePath: string, config: Required<VersionHistoryConfig>, options?: RestoreVersionOptions): Promise<RestoreResult>;
1043
1014
 
1044
1015
  /**
1045
- * @fileoverview Image handling for content collections
1016
+ * @fileoverview File watcher for detecting external changes
1046
1017
  *
1047
- * This module provides functions for uploading and managing images
1048
- * in content collections with support for different storage strategies.
1018
+ * This module provides file watching capabilities to detect when
1019
+ * content files are modified outside of the Writenex editor
1020
+ * (e.g., in VS Code or another editor).
1049
1021
  *
1050
- * ## Strategies:
1051
- * - colocated: Images stored alongside content files
1052
- * - public: Images stored in public directory
1053
- * - custom: User-defined storage paths
1022
+ * @module @writenex/astro/filesystem/watcher
1023
+ */
1024
+ /**
1025
+ * File change event types
1026
+ */
1027
+ type FileChangeType = "add" | "change" | "unlink";
1028
+ /**
1029
+ * File change event
1030
+ */
1031
+ interface FileChangeEvent {
1032
+ type: FileChangeType;
1033
+ path: string;
1034
+ collection: string;
1035
+ }
1036
+ /**
1037
+ * Watcher options
1038
+ */
1039
+ interface WatcherOptions {
1040
+ /** Callback when a file changes */
1041
+ onChange?: (event: FileChangeEvent) => void;
1042
+ /** Debounce delay in milliseconds */
1043
+ debounceMs?: number;
1044
+ /** Patterns to ignore */
1045
+ ignored?: string[];
1046
+ }
1047
+ /**
1048
+ * Content file watcher
1054
1049
  *
1055
- * @module @writenex/astro/filesystem/images
1050
+ * Watches the src/content directory for changes and emits events
1051
+ * when files are added, modified, or deleted.
1052
+ */
1053
+ declare class ContentWatcher {
1054
+ private watcher;
1055
+ private projectRoot;
1056
+ private contentDir;
1057
+ private options;
1058
+ private debounceTimers;
1059
+ constructor(projectRoot: string, contentDir?: string, options?: WatcherOptions);
1060
+ /**
1061
+ * Start watching for file changes
1062
+ */
1063
+ start(): void;
1064
+ /**
1065
+ * Stop watching for file changes
1066
+ */
1067
+ stop(): Promise<void>;
1068
+ /**
1069
+ * Handle a file change event
1070
+ */
1071
+ private handleChange;
1072
+ /**
1073
+ * Emit a file change event
1074
+ */
1075
+ private emitChange;
1076
+ /**
1077
+ * Check if the watcher is running
1078
+ */
1079
+ isWatching(): boolean;
1080
+ }
1081
+ /**
1082
+ * Track file modification times for conflict detection
1083
+ */
1084
+ declare class FileModificationTracker {
1085
+ private mtimes;
1086
+ /**
1087
+ * Record the current modification time of a file
1088
+ */
1089
+ track(filePath: string): Promise<void>;
1090
+ /**
1091
+ * Check if a file has been modified externally
1092
+ */
1093
+ hasExternalChanges(filePath: string): Promise<boolean>;
1094
+ /**
1095
+ * Clear tracking for a file
1096
+ */
1097
+ untrack(filePath: string): void;
1098
+ /**
1099
+ * Clear all tracking
1100
+ */
1101
+ clear(): void;
1102
+ }
1103
+ /**
1104
+ * Create a content watcher instance
1056
1105
  */
1106
+ declare function createContentWatcher(projectRoot: string, options?: WatcherOptions): ContentWatcher;
1057
1107
 
1058
1108
  /**
1059
- * Default image configuration
1109
+ * @fileoverview Filesystem writer for content operations
1110
+ *
1111
+ * This module provides functions for creating, updating, and deleting
1112
+ * content files in Astro content collections.
1113
+ *
1114
+ * ## Features:
1115
+ * - Create new content files with frontmatter
1116
+ * - Update existing content files
1117
+ * - Delete content files
1118
+ * - Generate unique slugs to avoid collisions
1119
+ * - Support for different file patterns (flat, folder-based, date-prefixed)
1120
+ * - Automatic version history creation before updates
1121
+ *
1122
+ * @module @writenex/astro/filesystem/writer
1060
1123
  */
1061
- declare const DEFAULT_IMAGE_CONFIG: ImageConfig;
1124
+
1062
1125
  /**
1063
- * Result of image upload operation
1126
+ * Options for creating content
1064
1127
  */
1065
- interface ImageUploadResult {
1066
- success: boolean;
1067
- /** Markdown-compatible path for the image */
1068
- path?: string;
1069
- /** Public URL for the image */
1070
- url?: string;
1071
- /** Error message if failed */
1072
- error?: string;
1128
+ interface CreateContentOptions {
1129
+ /** Frontmatter data */
1130
+ frontmatter: Record<string, unknown>;
1131
+ /** Markdown body content */
1132
+ body: string;
1133
+ /** Custom slug (optional, generated from title if not provided) */
1134
+ slug?: string;
1135
+ /** File pattern for the collection (e.g., "{slug}/index.md", "{date}-{slug}.md") */
1136
+ filePattern?: string;
1137
+ /** Custom token values to override automatic resolution */
1138
+ customTokens?: Record<string, string>;
1073
1139
  }
1074
1140
  /**
1075
- * Options for image upload
1141
+ * Options for updating content
1076
1142
  */
1077
- interface ImageUploadOptions {
1078
- /** Original filename */
1079
- filename: string;
1080
- /** Image binary data */
1081
- data: Buffer;
1082
- /** Collection name */
1083
- collection: string;
1084
- /** Content ID (slug) */
1085
- contentId: string;
1086
- /** Project root path */
1087
- projectRoot: string;
1088
- /** Image configuration */
1089
- config?: ImageConfig;
1143
+ interface UpdateContentOptions {
1144
+ /** Updated frontmatter data */
1145
+ frontmatter?: Record<string, unknown>;
1146
+ /** Updated markdown body content */
1147
+ body?: string;
1148
+ /** Project root for version history (required for version creation) */
1149
+ projectRoot?: string;
1150
+ /** Collection name for version history */
1151
+ collection?: string;
1152
+ /** Version history configuration */
1153
+ versionHistoryConfig?: Required<VersionHistoryConfig>;
1154
+ /**
1155
+ * Expected modification time for conflict detection.
1156
+ * If provided and the file's mtime differs, the update will fail with a conflict error.
1157
+ */
1158
+ expectedMtime?: number;
1090
1159
  }
1091
1160
  /**
1092
- * Validate image file
1093
- *
1094
- * @param filename - Original filename
1095
- * @returns True if valid image file
1161
+ * Result of a write operation
1096
1162
  */
1097
- declare function isValidImageFile(filename: string): boolean;
1163
+ interface WriteResult {
1164
+ /** Whether the operation was successful */
1165
+ success: boolean;
1166
+ /** The content ID (slug) */
1167
+ id?: string;
1168
+ /** The file path */
1169
+ path?: string;
1170
+ /** Error message if failed */
1171
+ error?: string;
1172
+ /** New modification time after write (for conflict detection) */
1173
+ mtime?: number;
1174
+ /** Conflict error if update failed due to external modification */
1175
+ conflict?: ContentConflictError;
1176
+ }
1098
1177
  /**
1099
- * Upload an image file
1100
- *
1101
- * @param options - Upload options
1102
- * @returns Upload result with paths
1103
- *
1104
- * @example
1105
- * ```typescript
1106
- * const result = await uploadImage({
1107
- * filename: "hero.jpg",
1108
- * data: imageBuffer,
1109
- * collection: "blog",
1110
- * contentId: "my-post",
1111
- * projectRoot: "/path/to/project",
1112
- * });
1178
+ * Generate a URL-safe slug from a string
1113
1179
  *
1114
- * if (result.success) {
1115
- * console.log(result.path); // "./my-post/hero-abc123.jpg"
1116
- * }
1117
- * ```
1180
+ * @param text - Text to slugify
1181
+ * @returns URL-safe slug
1118
1182
  */
1119
- declare function uploadImage(options: ImageUploadOptions): Promise<ImageUploadResult>;
1183
+ declare function generateSlug(text: string): string;
1120
1184
  /**
1121
- * Parse multipart form data for image upload
1122
- *
1123
- * Simple parser for multipart/form-data with single file upload.
1124
- * For production, consider using a proper multipart parser library.
1185
+ * Generate a unique slug that doesn't conflict with existing files
1125
1186
  *
1126
- * @param body - Raw request body
1127
- * @param contentType - Content-Type header
1128
- * @returns Parsed file data and fields
1129
- */
1130
- declare function parseMultipartFormData(body: Buffer, contentType: string): {
1131
- file?: {
1132
- filename: string;
1133
- data: Buffer;
1134
- contentType: string;
1135
- };
1136
- fields: Record<string, string>;
1137
- };
1138
- /**
1139
- * Content structure type detected from file path
1140
- */
1141
- type ContentStructure = "flat" | "folder-based" | "date-prefixed";
1142
- /**
1143
- * Result of content structure detection
1187
+ * @param baseSlug - The base slug to start with
1188
+ * @param collectionPath - Path to the collection directory
1189
+ * @param filePattern - File pattern (default: "{slug}.md")
1190
+ * @returns A unique slug
1144
1191
  */
1145
- interface ContentStructureResult {
1146
- /** Detected structure type */
1147
- structure: ContentStructure;
1148
- /** Path to the image folder (null if doesn't exist) */
1149
- imageFolderPath: string | null;
1150
- }
1192
+ declare function generateUniqueSlug(baseSlug: string, collectionPath: string, filePattern?: string): Promise<string>;
1151
1193
  /**
1152
- * Detect content structure and get the image folder path
1194
+ * Create a new content file in a collection
1153
1195
  *
1154
- * Handles three content structures:
1155
- * - Flat file: `my-post.md` -> looks for `my-post/` sibling folder
1156
- * - Folder-based: `slug/index.md` -> uses `slug/` parent folder
1157
- * - Date-prefixed: `2024-01-15-my-post.md` -> looks for `2024-01-15-my-post/` sibling folder
1196
+ * Supports various file patterns with automatic token resolution:
1197
+ * - `{slug}.md` - Simple flat structure (default)
1198
+ * - `{slug}/index.md` - Folder-based content
1199
+ * - `{date}-{slug}.md` - Date-prefixed naming
1200
+ * - `{year}/{slug}.md` - Year folder structure
1201
+ * - `{year}/{month}/{slug}.md` - Year/month folder structure
1202
+ * - `{year}/{month}/{day}/{slug}.md` - Full date folder structure
1203
+ * - `{lang}/{slug}.md` - Language-prefixed (i18n)
1204
+ * - `{category}/{slug}.md` - Category folder structure
1205
+ * - `{author}/{slug}.md` - Author folder structure
1206
+ * - Any custom pattern with tokens from frontmatter
1207
+ *
1208
+ * Token resolution priority:
1209
+ * 1. Custom tokens (explicitly provided via customTokens)
1210
+ * 2. Known token resolvers (date, year, month, lang, category, etc.)
1211
+ * 3. Frontmatter values (for custom tokens)
1212
+ * 4. Default values
1158
1213
  *
1159
1214
  * @param collectionPath - Absolute path to the collection directory
1160
- * @param contentId - Content ID (slug)
1161
- * @param contentFilePath - Absolute path to the content file
1162
- * @returns Path to the image folder, or null if no image folder exists
1215
+ * @param options - Content creation options
1216
+ * @returns WriteResult with success status and file info
1163
1217
  *
1164
1218
  * @example
1165
1219
  * ```typescript
1166
- * // Flat file structure
1167
- * const folder = getContentImageFolder(
1168
- * '/project/src/content/blog',
1169
- * 'my-post',
1170
- * '/project/src/content/blog/my-post.md'
1171
- * );
1172
- * // Returns: '/project/src/content/blog/my-post' (if exists)
1220
+ * // Flat structure
1221
+ * const result = await createContent('/project/src/content/blog', {
1222
+ * frontmatter: { title: 'My New Post', pubDate: new Date() },
1223
+ * body: '# Hello World',
1224
+ * });
1173
1225
  *
1174
1226
  * // Folder-based structure
1175
- * const folder = getContentImageFolder(
1176
- * '/project/src/content/blog',
1177
- * 'my-post',
1178
- * '/project/src/content/blog/my-post/index.md'
1179
- * );
1180
- * // Returns: '/project/src/content/blog/my-post'
1181
- * ```
1182
- */
1183
- declare function getContentImageFolder(collectionPath: string, contentId: string, contentFilePath: string): string | null;
1184
- /**
1185
- * Detect the content structure type from a content file path
1186
- *
1187
- * @param contentFilePath - Absolute path to the content file
1188
- * @returns The detected content structure type
1189
- */
1190
- declare function detectContentStructure(contentFilePath: string): ContentStructure;
1191
- /**
1192
- * Options for recursive directory scanning
1193
- */
1194
- interface ScanOptions {
1195
- /** Maximum recursion depth */
1196
- maxDepth: number;
1197
- /** Current recursion depth */
1198
- currentDepth: number;
1199
- /** Base path for calculating relative paths */
1200
- basePath: string;
1201
- }
1202
- /**
1203
- * Scan a directory recursively for image files
1204
- *
1205
- * Recursively scans the given directory for image files with supported extensions.
1206
- * Skips hidden folders (starting with .) and special folders (starting with _).
1207
- * Limits recursion to the specified maxDepth.
1227
+ * const result = await createContent('/project/src/content/blog', {
1228
+ * frontmatter: { title: 'My New Post', pubDate: new Date() },
1229
+ * body: '# Hello World',
1230
+ * filePattern: '{slug}/index.md',
1231
+ * });
1208
1232
  *
1209
- * @param dirPath - Absolute path to the directory to scan
1210
- * @param basePath - Base path for calculating relative paths
1211
- * @param options - Scan options including maxDepth and currentDepth
1212
- * @returns Array of discovered images
1233
+ * // i18n structure with custom token
1234
+ * const result = await createContent('/project/src/content/blog', {
1235
+ * frontmatter: { title: 'My New Post', lang: 'id' },
1236
+ * body: '# Hello World',
1237
+ * filePattern: '{lang}/{slug}.md',
1238
+ * });
1213
1239
  *
1214
- * @example
1215
- * ```typescript
1216
- * const images = await scanDirectoryForImages(
1217
- * '/project/src/content/blog/my-post',
1218
- * '/project/src/content/blog/my-post',
1219
- * { maxDepth: 5, currentDepth: 0, basePath: '/project/src/content/blog/my-post' }
1220
- * );
1240
+ * // Custom pattern with explicit token
1241
+ * const result = await createContent('/project/src/content/blog', {
1242
+ * frontmatter: { title: 'My New Post' },
1243
+ * body: '# Hello World',
1244
+ * filePattern: '{category}/{slug}.md',
1245
+ * customTokens: { category: 'tutorials' },
1246
+ * });
1221
1247
  * ```
1222
1248
  */
1223
- declare function scanDirectoryForImages(dirPath: string, basePath: string, options: ScanOptions): Promise<DiscoveredImage[]>;
1249
+ declare function createContent(collectionPath: string, options: CreateContentOptions): Promise<WriteResult>;
1224
1250
  /**
1225
- * Calculate relative path from content file to image file
1251
+ * Update an existing content file
1226
1252
  *
1227
- * Calculates the path that can be used in markdown to reference an image
1228
- * relative to the content file's location. The path always starts with ./
1229
- * to ensure it's treated as a relative reference.
1253
+ * Creates a version snapshot of the current content before updating
1254
+ * when version history is configured. Skips both version creation and
1255
+ * file write if the new content is identical to the current file content.
1256
+ * Version creation errors are logged but do not fail the save operation.
1230
1257
  *
1231
- * @param contentFilePath - Absolute path to the content file (e.g., /project/src/content/blog/my-post.md)
1232
- * @param imagePath - Absolute path to the image file (e.g., /project/src/content/blog/my-post/images/hero.jpg)
1233
- * @returns Relative path starting with ./ (e.g., ./my-post/images/hero.jpg)
1258
+ * @param filePath - Absolute path to the content file
1259
+ * @param collectionPath - Path to the collection directory
1260
+ * @param options - Update options including version history config
1261
+ * @returns WriteResult with success status
1234
1262
  *
1235
1263
  * @example
1236
1264
  * ```typescript
1237
- * // Flat file structure
1238
- * const relPath = calculateRelativePath(
1265
+ * const result = await updateContent(
1239
1266
  * '/project/src/content/blog/my-post.md',
1240
- * '/project/src/content/blog/my-post/hero.jpg'
1241
- * );
1242
- * // Returns: './my-post/hero.jpg'
1243
- *
1244
- * // Folder-based structure
1245
- * const relPath = calculateRelativePath(
1246
- * '/project/src/content/blog/my-post/index.md',
1247
- * '/project/src/content/blog/my-post/images/hero.jpg'
1248
- * );
1249
- * // Returns: './images/hero.jpg'
1250
- *
1251
- * // Nested subfolder
1252
- * const relPath = calculateRelativePath(
1253
- * '/project/src/content/blog/my-post/index.md',
1254
- * '/project/src/content/blog/my-post/assets/photos/hero.jpg'
1267
+ * '/project/src/content/blog',
1268
+ * {
1269
+ * frontmatter: { title: 'Updated Title' },
1270
+ * body: '# Updated Content',
1271
+ * projectRoot: '/project',
1272
+ * collection: 'blog',
1273
+ * versionHistoryConfig: { enabled: true, maxVersions: 20, storagePath: '.writenex/versions' },
1274
+ * }
1255
1275
  * );
1256
- * // Returns: './assets/photos/hero.jpg'
1257
1276
  * ```
1258
1277
  */
1259
- declare function calculateRelativePath(contentFilePath: string, imagePath: string): string;
1278
+ declare function updateContent(filePath: string, collectionPath: string, options: UpdateContentOptions): Promise<WriteResult>;
1260
1279
  /**
1261
- * Discover all images associated with a content item
1262
- *
1263
- * This is the main entry point for image discovery. It:
1264
- * 1. Locates the content file using getContentFilePath
1265
- * 2. Determines the image folder using getContentImageFolder
1266
- * 3. Scans the folder recursively using scanDirectoryForImages
1267
- * 4. Calculates relative paths for all discovered images
1280
+ * Delete a content file
1268
1281
  *
1269
- * @param collectionPath - Absolute path to the collection directory
1270
- * @param contentId - Content ID (slug)
1271
- * @param options - Optional discovery options
1272
- * @returns ImageDiscoveryResult with discovered images or error
1282
+ * @param filePath - Absolute path to the content file
1283
+ * @returns WriteResult with success status
1273
1284
  *
1274
1285
  * @example
1275
1286
  * ```typescript
1276
- * const result = await discoverContentImages(
1277
- * '/project/src/content/blog',
1278
- * 'my-post',
1279
- * { maxDepth: 3 }
1280
- * );
1281
- *
1282
- * if (result.success) {
1283
- * console.log(`Found ${result.images.length} images`);
1284
- * for (const img of result.images) {
1285
- * console.log(`- ${img.relativePath}`);
1286
- * }
1287
- * }
1287
+ * const result = await deleteContent('/project/src/content/blog/my-post.md');
1288
1288
  * ```
1289
1289
  */
1290
- declare function discoverContentImages(collectionPath: string, contentId: string, options?: ImageDiscoveryOptions): Promise<ImageDiscoveryResult>;
1290
+ declare function deleteContent(filePath: string): Promise<WriteResult>;
1291
1291
 
1292
1292
  export { type ContentStructure, type ContentStructureResult, ContentWatcher, type CreateContentOptions, DEFAULT_IMAGE_CONFIG, type FileChangeEvent, type FileChangeType, FileModificationTracker, type ImageUploadOptions, type ImageUploadResult, type ReadContentOptions, type ReadFileResult, type UpdateContentOptions, type WatcherOptions, type WriteResult, calculateRelativePath, checkCollection, clearVersions, clearVersionsWithConfig, createContent, createContentWatcher, createEmptyManifest, deleteContent, deleteVersion, deleteVersionWithConfig, detectContentStructure, discoverContentImages, ensureGitignore, ensureStorageDirectory, extractSlug, generateExcerpt, generatePreview, generateSlug, generateUniqueSlug, generateVersionId, getCollectionCount, getCollectionSummaries, getContentFilePath, getContentImageFolder, getFileStats, getManifestPath, getOrRecoverManifest, getVersion, getVersionFilePath, getVersionStoragePath, getVersionWithConfig, getVersions, getVersionsWithConfig, isContentFile, isValidImageFile, isVersionHistoryEnabled, parseMultipartFormData, parseVersionId, pruneVersions, pruneVersionsWithConfig, readCollection, readContentFile, readManifest, recoverManifest, resolveVersionConfig, restoreVersion, restoreVersionWithConfig, saveVersion, saveVersionWithConfig, scanDirectoryForImages, toContentSummary, updateContent, uploadImage, writeManifest };