@moxn/kb-migrate 0.4.34 → 0.4.36
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/api-error.d.ts +14 -0
- package/dist/api-error.js +19 -0
- package/dist/api-error.test.d.ts +1 -0
- package/dist/api-error.test.js +26 -0
- package/dist/client.d.ts +61 -0
- package/dist/client.js +96 -0
- package/dist/export.d.ts +11 -3
- package/dist/export.js +96 -159
- package/dist/export.test.d.ts +1 -0
- package/dist/export.test.js +107 -0
- package/dist/grammar-media.d.ts +52 -0
- package/dist/grammar-media.js +77 -0
- package/dist/grammar-media.test.d.ts +1 -0
- package/dist/grammar-media.test.js +108 -0
- package/dist/import-local.d.ts +41 -0
- package/dist/import-local.js +206 -0
- package/dist/import-local.test.d.ts +1 -0
- package/dist/import-local.test.js +19 -0
- package/dist/index.js +8 -9
- package/dist/sources/local.d.ts +24 -0
- package/dist/sources/local.js +22 -0
- package/dist/sources/local.test.js +1 -1
- package/dist/sources/notion.d.ts +7 -1
- package/dist/sources/notion.js +14 -8
- package/dist/sources/notion.test.d.ts +1 -0
- package/dist/sources/notion.test.js +45 -0
- package/package.json +9 -1
package/dist/sources/local.d.ts
CHANGED
|
@@ -29,6 +29,22 @@ export interface LocalSourceConfig extends SourceConfig {
|
|
|
29
29
|
/** Date filter for source files */
|
|
30
30
|
dateFilter?: DateFilter;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* A local file to be imported as GRAMMAR markdown (Phase 9a-3).
|
|
34
|
+
*
|
|
35
|
+
* The grammar path reads the file VERBATIM (it is already moxn-grammar
|
|
36
|
+
* markdown — front-matter + body + `:::…` embeds, e.g. produced by the
|
|
37
|
+
* export) and hands it to `import_markdown`; it does NOT parse to MCP blocks
|
|
38
|
+
* like the legacy {@link LocalSource.extract} path.
|
|
39
|
+
*/
|
|
40
|
+
export interface GrammarFile {
|
|
41
|
+
/** Absolute path to the source file on disk (for reading + media base dir). */
|
|
42
|
+
fullPath: string;
|
|
43
|
+
/** Original relative path from the scan root (for logging). */
|
|
44
|
+
sourcePath: string;
|
|
45
|
+
/** Sanitized KB path the doc maps to (e.g. "subdir/My-Doc"). */
|
|
46
|
+
kbPath: string;
|
|
47
|
+
}
|
|
32
48
|
/**
|
|
33
49
|
* Local filesystem migration source
|
|
34
50
|
*/
|
|
@@ -40,6 +56,14 @@ export declare class LocalSource extends MigrationSource<LocalSourceConfig> {
|
|
|
40
56
|
validate(): Promise<void>;
|
|
41
57
|
getDocumentCount(): Promise<number | undefined>;
|
|
42
58
|
extract(): AsyncGenerator<ExtractedDocument, void, unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* Yield local files as GRAMMAR markdown for the `import_markdown` path
|
|
61
|
+
* (Phase 9a-3). Reuses the SAME discovery + collision-skip + date-filter
|
|
62
|
+
* logic as {@link extract}, but emits the file's KB path + disk location
|
|
63
|
+
* instead of parsing to MCP blocks. The runner reads the file verbatim,
|
|
64
|
+
* uploads + rewrites its media refs, then calls `import_markdown`.
|
|
65
|
+
*/
|
|
66
|
+
extractGrammarFiles(): AsyncGenerator<GrammarFile, void, unknown>;
|
|
43
67
|
private discoverFiles;
|
|
44
68
|
private extractDocument;
|
|
45
69
|
private parseMarkdownSections;
|
package/dist/sources/local.js
CHANGED
|
@@ -75,6 +75,28 @@ export class LocalSource extends MigrationSource {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Yield local files as GRAMMAR markdown for the `import_markdown` path
|
|
80
|
+
* (Phase 9a-3). Reuses the SAME discovery + collision-skip + date-filter
|
|
81
|
+
* logic as {@link extract}, but emits the file's KB path + disk location
|
|
82
|
+
* instead of parsing to MCP blocks. The runner reads the file verbatim,
|
|
83
|
+
* uploads + rewrites its media refs, then calls `import_markdown`.
|
|
84
|
+
*/
|
|
85
|
+
async *extractGrammarFiles() {
|
|
86
|
+
if (!this.files) {
|
|
87
|
+
await this.discoverFiles();
|
|
88
|
+
}
|
|
89
|
+
for (const file of this.files) {
|
|
90
|
+
if (this.skippedCollisions.has(file)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
yield {
|
|
94
|
+
fullPath: path.join(this.config.directory, file),
|
|
95
|
+
sourcePath: file,
|
|
96
|
+
kbPath: filePathToKbPath(file),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
78
100
|
async discoverFiles() {
|
|
79
101
|
const patterns = this.config.extensions.map((ext) => `**/*${ext.startsWith('.') ? ext : '.' + ext}`);
|
|
80
102
|
const allFiles = [];
|
|
@@ -20,7 +20,7 @@ describe('sanitizePathSegment', () => {
|
|
|
20
20
|
expect(sanitizePathSegment('foo@bar!')).toBe('foo-bar');
|
|
21
21
|
});
|
|
22
22
|
it('handles parentheses, ampersands, apostrophes', () => {
|
|
23
|
-
expect(sanitizePathSegment(
|
|
23
|
+
expect(sanitizePathSegment('Q3 Plan (final)')).toBe('Q3-Plan-final');
|
|
24
24
|
expect(sanitizePathSegment("Tom's & Jerry's")).toBe('Tom-s-Jerry-s');
|
|
25
25
|
});
|
|
26
26
|
it('strips accents via NFKD normalization', () => {
|
package/dist/sources/notion.d.ts
CHANGED
|
@@ -127,6 +127,12 @@ export interface PageTreeResult {
|
|
|
127
127
|
export declare function buildPageTree(pages: NotionPage[], options?: BuildPageTreeOptions): PageTreeResult;
|
|
128
128
|
/**
|
|
129
129
|
* Slugify a page title for use as a KB path segment.
|
|
130
|
-
*
|
|
130
|
+
*
|
|
131
|
+
* Server's validatePathForLtree only allows [a-zA-Z0-9-]. We:
|
|
132
|
+
* - lowercase
|
|
133
|
+
* - normalize NFKD + strip combining marks so "résumé" → "resume"
|
|
134
|
+
* - replace anything not in [a-z0-9-] with "-"
|
|
135
|
+
* - collapse runs of "-" and trim leading/trailing "-"
|
|
136
|
+
* Falls back to "untitled" when the result is empty (e.g. emoji-only or non-Latin titles).
|
|
131
137
|
*/
|
|
132
138
|
export declare function slugify(title: string): string;
|
package/dist/sources/notion.js
CHANGED
|
@@ -596,15 +596,21 @@ function getParentPageId(page) {
|
|
|
596
596
|
// ============================================
|
|
597
597
|
/**
|
|
598
598
|
* Slugify a page title for use as a KB path segment.
|
|
599
|
-
*
|
|
599
|
+
*
|
|
600
|
+
* Server's validatePathForLtree only allows [a-zA-Z0-9-]. We:
|
|
601
|
+
* - lowercase
|
|
602
|
+
* - normalize NFKD + strip combining marks so "résumé" → "resume"
|
|
603
|
+
* - replace anything not in [a-z0-9-] with "-"
|
|
604
|
+
* - collapse runs of "-" and trim leading/trailing "-"
|
|
605
|
+
* Falls back to "untitled" when the result is empty (e.g. emoji-only or non-Latin titles).
|
|
600
606
|
*/
|
|
601
607
|
export function slugify(title) {
|
|
602
|
-
|
|
608
|
+
const cleaned = title
|
|
609
|
+
.normalize('NFKD')
|
|
610
|
+
.replace(/[̀-ͯ]/g, '')
|
|
603
611
|
.toLowerCase()
|
|
604
|
-
.
|
|
605
|
-
.replace(
|
|
606
|
-
.replace(
|
|
607
|
-
|
|
608
|
-
.replace(/^-|-$/g, '') || // Trim leading/trailing hyphens
|
|
609
|
-
'untitled');
|
|
612
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
613
|
+
.replace(/-+/g, '-')
|
|
614
|
+
.replace(/^-+|-+$/g, '');
|
|
615
|
+
return cleaned.length > 0 ? cleaned : 'untitled';
|
|
610
616
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { slugify } from './notion.js';
|
|
3
|
+
describe('slugify (notion)', () => {
|
|
4
|
+
it('lowercases', () => {
|
|
5
|
+
expect(slugify('MyDoc')).toBe('mydoc');
|
|
6
|
+
});
|
|
7
|
+
it('replaces spaces with hyphens', () => {
|
|
8
|
+
expect(slugify('My Doc')).toBe('my-doc');
|
|
9
|
+
});
|
|
10
|
+
it('replaces underscores with hyphens (ltree forbids _)', () => {
|
|
11
|
+
expect(slugify('my_doc')).toBe('my-doc');
|
|
12
|
+
});
|
|
13
|
+
it('replaces periods with hyphens (ltree forbids .)', () => {
|
|
14
|
+
expect(slugify('release.v2')).toBe('release-v2');
|
|
15
|
+
});
|
|
16
|
+
it('handles parens, ampersands, apostrophes', () => {
|
|
17
|
+
expect(slugify('Q3 Plan (final)')).toBe('q3-plan-final');
|
|
18
|
+
expect(slugify("Tom's & Jerry's")).toBe('tom-s-jerry-s');
|
|
19
|
+
});
|
|
20
|
+
it('strips accents via NFKD normalization', () => {
|
|
21
|
+
expect(slugify('résumé')).toBe('resume');
|
|
22
|
+
expect(slugify('café')).toBe('cafe');
|
|
23
|
+
expect(slugify('naïve')).toBe('naive');
|
|
24
|
+
});
|
|
25
|
+
it('replaces non-Latin scripts with hyphens', () => {
|
|
26
|
+
expect(slugify('файл')).toBe('untitled');
|
|
27
|
+
expect(slugify('日本語')).toBe('untitled');
|
|
28
|
+
});
|
|
29
|
+
it('preserves digits', () => {
|
|
30
|
+
expect(slugify('Q3-2024')).toBe('q3-2024');
|
|
31
|
+
});
|
|
32
|
+
it('collapses runs of separators', () => {
|
|
33
|
+
expect(slugify('foo___bar...baz')).toBe('foo-bar-baz');
|
|
34
|
+
});
|
|
35
|
+
it('trims leading/trailing hyphens', () => {
|
|
36
|
+
expect(slugify('-foo-')).toBe('foo');
|
|
37
|
+
expect(slugify(' hello ')).toBe('hello');
|
|
38
|
+
});
|
|
39
|
+
it('falls back to "untitled" for empty result', () => {
|
|
40
|
+
expect(slugify('')).toBe('untitled');
|
|
41
|
+
expect(slugify('___')).toBe('untitled');
|
|
42
|
+
expect(slugify('!!!')).toBe('untitled');
|
|
43
|
+
expect(slugify('🎉')).toBe('untitled');
|
|
44
|
+
});
|
|
45
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moxn/kb-migrate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.36",
|
|
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",
|
|
@@ -70,6 +70,14 @@
|
|
|
70
70
|
"types": "./dist/client.d.ts",
|
|
71
71
|
"default": "./dist/client.js"
|
|
72
72
|
},
|
|
73
|
+
"./grammar-media": {
|
|
74
|
+
"types": "./dist/grammar-media.d.ts",
|
|
75
|
+
"default": "./dist/grammar-media.js"
|
|
76
|
+
},
|
|
77
|
+
"./import-local": {
|
|
78
|
+
"types": "./dist/import-local.d.ts",
|
|
79
|
+
"default": "./dist/import-local.js"
|
|
80
|
+
},
|
|
73
81
|
"./date-filter": {
|
|
74
82
|
"types": "./dist/date-filter.d.ts",
|
|
75
83
|
"default": "./dist/date-filter.js"
|