@moxn/kb-migrate 0.2.2 → 0.3.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/dist/client.d.ts CHANGED
@@ -28,6 +28,13 @@ export declare class MoxnClient {
28
28
  private buildPath;
29
29
  private processSections;
30
30
  private processContentBlocks;
31
+ /**
32
+ * Upload a file to Moxn storage and return the storage key.
33
+ */
34
+ uploadFile(data: Buffer, mimeType: string, filename?: string): Promise<{
35
+ key: string;
36
+ }>;
37
+ private getUploadUrl;
31
38
  private createDocument;
32
39
  private updateDocument;
33
40
  private isConflictError;
package/dist/client.js CHANGED
@@ -179,20 +179,69 @@ export class MoxnClient {
179
179
  }
180
180
  async processContentBlocks(blocks) {
181
181
  return Promise.all(blocks.map(async (block) => {
182
+ // Handle image files - upload to storage
182
183
  if (block.blockType === 'image' && block.type === 'file' && block.path) {
183
- // Convert local file to base64
184
184
  const data = await fs.readFile(block.path);
185
+ const filename = block.path.split('/').pop();
186
+ const { key } = await this.uploadFile(data, block.mediaType, filename);
185
187
  return {
186
188
  blockType: block.blockType,
187
- type: 'base64',
188
- base64: data.toString('base64'),
189
+ type: 'storage',
190
+ key,
189
191
  mediaType: block.mediaType,
190
192
  alt: block.alt,
191
193
  };
192
194
  }
195
+ // Handle CSV files - upload to storage
196
+ if (block.blockType === 'csv' && block.type === 'file' && block.path) {
197
+ const data = await fs.readFile(block.path);
198
+ const { key } = await this.uploadFile(data, 'text/csv', block.filename);
199
+ return {
200
+ blockType: block.blockType,
201
+ type: 'storage',
202
+ key,
203
+ mediaType: 'text/csv',
204
+ filename: block.filename,
205
+ headers: block.headers,
206
+ rowCount: block.rowCount,
207
+ };
208
+ }
193
209
  return block;
194
210
  }));
195
211
  }
212
+ /**
213
+ * Upload a file to Moxn storage and return the storage key.
214
+ */
215
+ async uploadFile(data, mimeType, filename) {
216
+ // 1. Get presigned upload URL
217
+ const { key, uploadUrl } = await this.getUploadUrl(mimeType, filename);
218
+ // 2. PUT file to presigned URL
219
+ const response = await fetch(uploadUrl, {
220
+ method: 'PUT',
221
+ headers: { 'Content-Type': mimeType },
222
+ body: new Uint8Array(data),
223
+ });
224
+ if (!response.ok) {
225
+ throw new Error(`File upload failed: ${response.status}`);
226
+ }
227
+ return { key };
228
+ }
229
+ async getUploadUrl(type, filename) {
230
+ const response = await fetch(`${this.apiUrl}/api/v1/kb/upload`, {
231
+ method: 'POST',
232
+ headers: {
233
+ 'Content-Type': 'application/json',
234
+ 'x-api-key': this.apiKey,
235
+ },
236
+ body: JSON.stringify({ type, filename }),
237
+ });
238
+ if (!response.ok) {
239
+ const body = await response.text();
240
+ throw new Error(`Upload URL request failed: ${response.status} ${body}`);
241
+ }
242
+ const data = await response.json();
243
+ return { key: data.key, uploadUrl: data.uploadUrl };
244
+ }
196
245
  async createDocument(request) {
197
246
  const response = await fetch(`${this.apiUrl}/api/v1/kb/documents`, {
198
247
  method: 'POST',
@@ -27,10 +27,14 @@ export declare class LocalSource extends MigrationSource<LocalSourceConfig> {
27
27
  private parseMarkdownSections;
28
28
  private nodesToContentBlocks;
29
29
  /**
30
- * Extract image nodes from paragraph children, returning image blocks
31
- * and whether non-image text content exists.
30
+ * Extract image nodes and CSV links from paragraph children, returning media blocks
31
+ * and whether non-media text content exists.
32
32
  */
33
33
  private extractImagesFromParagraph;
34
+ /**
35
+ * Convert a markdown link to a CSV block if it points to a local CSV file.
36
+ */
37
+ private linkToCSVBlock;
34
38
  private imageToBlock;
35
39
  private guessImageType;
36
40
  private extensionToMediaType;
@@ -4,6 +4,7 @@
4
4
  * Extracts documents from local markdown and text files.
5
5
  */
6
6
  import * as fs from 'fs/promises';
7
+ import * as fsSync from 'fs';
7
8
  import * as path from 'path';
8
9
  import { glob } from 'glob';
9
10
  import { unified } from 'unified';
@@ -85,7 +86,6 @@ export class LocalSource extends MigrationSource {
85
86
  }
86
87
  async extractDocument(relativePath) {
87
88
  const fullPath = path.join(this.config.directory, relativePath);
88
- const content = await fs.readFile(fullPath, 'utf-8');
89
89
  // Parse relative path to create document path
90
90
  const parsed = path.parse(relativePath);
91
91
  const dirParts = parsed.dir ? parsed.dir.split(path.sep) : [];
@@ -100,6 +100,7 @@ export class LocalSource extends MigrationSource {
100
100
  .split(/[-_]/)
101
101
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
102
102
  .join(' ');
103
+ const content = await fs.readFile(fullPath, 'utf-8');
103
104
  // Parse into sections
104
105
  const sections = this.parseMarkdownSections(content, path.dirname(fullPath));
105
106
  if (sections.length === 0) {
@@ -218,8 +219,8 @@ export class LocalSource extends MigrationSource {
218
219
  return blocks;
219
220
  }
220
221
  /**
221
- * Extract image nodes from paragraph children, returning image blocks
222
- * and whether non-image text content exists.
222
+ * Extract image nodes and CSV links from paragraph children, returning media blocks
223
+ * and whether non-media text content exists.
223
224
  */
224
225
  extractImagesFromParagraph(children, baseDir) {
225
226
  const images = [];
@@ -231,6 +232,20 @@ export class LocalSource extends MigrationSource {
231
232
  images.push(imageBlock);
232
233
  }
233
234
  }
235
+ else if (child.type === 'link') {
236
+ // Check if this is a link to a CSV file
237
+ const csvBlock = this.linkToCSVBlock(child, baseDir);
238
+ if (csvBlock) {
239
+ images.push(csvBlock);
240
+ }
241
+ else {
242
+ // Regular link, treat as text
243
+ const text = this.nodeToMarkdown(child).trim();
244
+ if (text) {
245
+ hasText = true;
246
+ }
247
+ }
248
+ }
234
249
  else {
235
250
  // Check if the child has any meaningful text
236
251
  const text = this.nodeToMarkdown(child).trim();
@@ -241,6 +256,36 @@ export class LocalSource extends MigrationSource {
241
256
  }
242
257
  return { images, hasText };
243
258
  }
259
+ /**
260
+ * Convert a markdown link to a CSV block if it points to a local CSV file.
261
+ */
262
+ linkToCSVBlock(node, baseDir) {
263
+ const href = node.url;
264
+ // Only handle local CSV files
265
+ if (href.startsWith('http://') || href.startsWith('https://')) {
266
+ return null;
267
+ }
268
+ if (!href.toLowerCase().endsWith('.csv')) {
269
+ return null;
270
+ }
271
+ const csvPath = path.isAbsolute(href) ? href : path.join(baseDir, href);
272
+ if (!fsSync.existsSync(csvPath)) {
273
+ return null;
274
+ }
275
+ // Read CSV for metadata
276
+ const content = fsSync.readFileSync(csvPath, 'utf-8');
277
+ const lines = content.split('\n').filter((l) => l.trim());
278
+ const headers = lines[0]?.split(',').map((h) => h.trim()) || [];
279
+ return {
280
+ blockType: 'csv',
281
+ type: 'file',
282
+ path: csvPath,
283
+ mediaType: 'text/csv',
284
+ filename: path.basename(csvPath),
285
+ headers,
286
+ rowCount: Math.max(0, lines.length - 1),
287
+ };
288
+ }
244
289
  imageToBlock(node, baseDir) {
245
290
  const url = node.url;
246
291
  // Handle external URLs
package/dist/types.d.ts CHANGED
@@ -5,15 +5,19 @@
5
5
  * A content block in a section
6
6
  */
7
7
  export interface ContentBlock {
8
- blockType: 'text' | 'image' | 'document';
8
+ blockType: 'text' | 'image' | 'document' | 'csv';
9
9
  text?: string;
10
- type?: 'base64' | 'url' | 'file';
10
+ type?: 'base64' | 'url' | 'file' | 'storage';
11
11
  mediaType?: string;
12
12
  path?: string;
13
13
  url?: string;
14
14
  base64?: string;
15
+ key?: string;
15
16
  alt?: string;
16
17
  filename?: string;
18
+ /** CSV-specific metadata */
19
+ headers?: string[];
20
+ rowCount?: number;
17
21
  }
18
22
  /**
19
23
  * A section to be created in a document
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moxn/kb-migrate",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Migration tool for importing documents into Moxn Knowledge Base from local files, Notion, Google Docs, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",