@moxn/kb-migrate 0.4.28 → 0.4.30
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/sources/notion-api.js +20 -5
- package/dist/sources/notion-blocks.js +1 -1
- package/dist/sources/onenote/__tests__/onenote-html.test.d.ts +1 -0
- package/dist/sources/onenote/__tests__/onenote-html.test.js +234 -0
- package/dist/sources/onenote/index.d.ts +13 -0
- package/dist/sources/onenote/index.js +8 -0
- package/dist/sources/onenote/onenote-api.d.ts +63 -0
- package/dist/sources/onenote/onenote-api.js +147 -0
- package/dist/sources/onenote/onenote-auth.d.ts +83 -0
- package/dist/sources/onenote/onenote-auth.js +136 -0
- package/dist/sources/onenote/onenote-html.d.ts +54 -0
- package/dist/sources/onenote/onenote-html.js +619 -0
- package/dist/sources/onenote/onenote-tree.d.ts +38 -0
- package/dist/sources/onenote/onenote-tree.js +131 -0
- package/dist/sources/onenote/types.d.ts +124 -0
- package/dist/sources/onenote/types.js +8 -0
- package/package.json +24 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery: walk Microsoft Graph's OneNote hierarchy and flatten into a
|
|
3
|
+
* list of pages with their computed KB path.
|
|
4
|
+
*
|
|
5
|
+
* The hierarchy is: Notebook → SectionGroup* → Section → Page.
|
|
6
|
+
* SectionGroups can nest; OneNote caps at 4 levels of nesting in practice
|
|
7
|
+
* and we cap recursion at 4 to avoid accidental cycles.
|
|
8
|
+
*
|
|
9
|
+
* Selection semantics: callers pass a set of selected Graph IDs (any mix of
|
|
10
|
+
* notebook / sectionGroup / section / page). A page is included iff its
|
|
11
|
+
* own ID or any ancestor's ID is in the set. If the set is empty, every
|
|
12
|
+
* page is included.
|
|
13
|
+
*/
|
|
14
|
+
const MAX_GROUP_DEPTH = 4;
|
|
15
|
+
/**
|
|
16
|
+
* Normalize any human text into a ltree-safe slug segment.
|
|
17
|
+
* - lowercases
|
|
18
|
+
* - replaces spaces/underscores/periods with hyphens (ltree forbids _ and .)
|
|
19
|
+
* - drops anything outside [a-z0-9-]
|
|
20
|
+
* - collapses runs of hyphens; trims leading/trailing hyphens
|
|
21
|
+
* Falls back to `untitled` when the result is empty.
|
|
22
|
+
*/
|
|
23
|
+
export function slugifyPathSegment(raw) {
|
|
24
|
+
const ascii = raw
|
|
25
|
+
.normalize('NFKD')
|
|
26
|
+
.replace(/[̀-ͯ]/g, '') // strip combining marks
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/[\s_.]+/g, '-')
|
|
29
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
30
|
+
.replace(/-+/g, '-')
|
|
31
|
+
.replace(/^-+|-+$/g, '');
|
|
32
|
+
return ascii.length > 0 ? ascii : 'untitled';
|
|
33
|
+
}
|
|
34
|
+
export async function discoverOneNotePages(client, opts = {}) {
|
|
35
|
+
const pathPrefix = opts.pathPrefix
|
|
36
|
+
? opts.pathPrefix.replace(/^\/+|\/+$/g, '')
|
|
37
|
+
: 'imported/onenote';
|
|
38
|
+
const sel = opts.selectedIds && opts.selectedIds.size > 0 ? opts.selectedIds : null;
|
|
39
|
+
const notebooks = await client.listNotebooks();
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const notebook of notebooks) {
|
|
42
|
+
const notebookSelected = !sel || sel.has(notebook.id);
|
|
43
|
+
// When nothing at notebook-or-below is selected we can skip entirely.
|
|
44
|
+
// Cheap upper bound: if sel is set and notebook isn't in it, we still
|
|
45
|
+
// need to walk in case a descendant is selected.
|
|
46
|
+
const notebookSlug = slugifyPathSegment(notebook.displayName);
|
|
47
|
+
await walkNotebook(client, notebook, [pathPrefix, notebookSlug], notebookSelected, sel, opts, out);
|
|
48
|
+
}
|
|
49
|
+
out.sort((a, b) => a.kbPath.localeCompare(b.kbPath));
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
async function walkNotebook(client, notebook, pathSegs, ancestorSelected, sel, opts, out) {
|
|
53
|
+
const [sectionGroups, sections] = await Promise.all([
|
|
54
|
+
client.listSectionGroups(notebook.id),
|
|
55
|
+
client.listSectionsInNotebook(notebook.id),
|
|
56
|
+
]);
|
|
57
|
+
const ctx = {
|
|
58
|
+
notebookId: notebook.id,
|
|
59
|
+
notebookName: notebook.displayName,
|
|
60
|
+
sectionGroups: [],
|
|
61
|
+
};
|
|
62
|
+
for (const section of sections) {
|
|
63
|
+
const sectionSelected = ancestorSelected || (!!sel && sel.has(section.id));
|
|
64
|
+
await walkSection(client, section, pathSegs, ctx, sectionSelected, sel, opts, out);
|
|
65
|
+
}
|
|
66
|
+
for (const group of sectionGroups) {
|
|
67
|
+
await walkSectionGroup(client, group, pathSegs, ctx, ancestorSelected, sel, opts, out, 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function walkSectionGroup(client, group, pathSegs, ctx, ancestorSelected, sel, opts, out, depth) {
|
|
71
|
+
if (depth > MAX_GROUP_DEPTH)
|
|
72
|
+
return;
|
|
73
|
+
const selfSelected = ancestorSelected || (!!sel && sel.has(group.id));
|
|
74
|
+
const nextSegs = [...pathSegs, slugifyPathSegment(group.displayName)];
|
|
75
|
+
const nextCtx = {
|
|
76
|
+
...ctx,
|
|
77
|
+
sectionGroups: [...ctx.sectionGroups, { id: group.id, name: group.displayName }],
|
|
78
|
+
};
|
|
79
|
+
const [nestedGroups, groupSections] = await Promise.all([
|
|
80
|
+
client.listNestedSectionGroups(group.id),
|
|
81
|
+
client.listSectionsInGroup(group.id),
|
|
82
|
+
]);
|
|
83
|
+
for (const section of groupSections) {
|
|
84
|
+
const sectionSelected = selfSelected || (!!sel && sel.has(section.id));
|
|
85
|
+
await walkSection(client, section, nextSegs, nextCtx, sectionSelected, sel, opts, out);
|
|
86
|
+
}
|
|
87
|
+
for (const nested of nestedGroups) {
|
|
88
|
+
await walkSectionGroup(client, nested, nextSegs, nextCtx, selfSelected, sel, opts, out, depth + 1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function walkSection(client, section, pathSegs, ctx, ancestorSelected, sel, opts, out) {
|
|
92
|
+
const pages = await client.listPages(section.id, {
|
|
93
|
+
modifiedAfter: opts.modifiedAfter,
|
|
94
|
+
modifiedBefore: opts.modifiedBefore,
|
|
95
|
+
});
|
|
96
|
+
const sectionSeg = slugifyPathSegment(section.displayName);
|
|
97
|
+
for (const page of pages) {
|
|
98
|
+
const pageSelected = ancestorSelected || (!!sel && sel.has(page.id));
|
|
99
|
+
if (!pageSelected)
|
|
100
|
+
continue;
|
|
101
|
+
const pageSeg = slugifyPathSegment(page.title ?? 'untitled');
|
|
102
|
+
out.push(toDiscovered(page, section, [...pathSegs, sectionSeg, pageSeg], ctx));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function toDiscovered(page, section, segs, ctx) {
|
|
106
|
+
return {
|
|
107
|
+
onenotePageId: page.id,
|
|
108
|
+
title: page.title ?? 'Untitled',
|
|
109
|
+
kbPath: '/' + segs.join('/'),
|
|
110
|
+
notebookId: ctx.notebookId,
|
|
111
|
+
notebookName: ctx.notebookName,
|
|
112
|
+
sectionId: section.id,
|
|
113
|
+
sectionName: section.displayName,
|
|
114
|
+
sectionGroups: ctx.sectionGroups,
|
|
115
|
+
onenoteWebUrl: page.links?.oneNoteWebUrl?.href,
|
|
116
|
+
createdDateTime: page.createdDateTime,
|
|
117
|
+
lastModifiedDateTime: page.lastModifiedDateTime,
|
|
118
|
+
createdByEmail: page.createdBy?.user?.email ?? page.createdBy?.user?.userPrincipalName,
|
|
119
|
+
createdByDisplayName: page.createdBy?.user?.displayName,
|
|
120
|
+
lastModifiedByEmail: page.lastModifiedBy?.user?.email ??
|
|
121
|
+
page.lastModifiedBy?.user?.userPrincipalName,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Join kbPath segments into `/a/b/c` — public helper used by the orchestrator
|
|
126
|
+
* when it needs to derive a path outside the discovery walk.
|
|
127
|
+
*/
|
|
128
|
+
export function joinKbPath(...segments) {
|
|
129
|
+
const filtered = segments.filter((s) => Boolean(s && s.length));
|
|
130
|
+
return '/' + filtered.map((s) => s.replace(/^\/+|\/+$/g, '')).join('/');
|
|
131
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the OneNote source (Microsoft Graph).
|
|
3
|
+
*
|
|
4
|
+
* These mirror the Graph API payloads we actually read. Graph's own TypeScript
|
|
5
|
+
* types are available via @microsoft/microsoft-graph-types but include every
|
|
6
|
+
* field of every shape; we trim to what the importer touches.
|
|
7
|
+
*/
|
|
8
|
+
export interface GraphIdentity {
|
|
9
|
+
user?: {
|
|
10
|
+
id?: string;
|
|
11
|
+
displayName?: string;
|
|
12
|
+
email?: string;
|
|
13
|
+
/** work/school accounts — full UPN */
|
|
14
|
+
userPrincipalName?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface OneNoteNotebook {
|
|
18
|
+
id: string;
|
|
19
|
+
displayName: string;
|
|
20
|
+
isDefault?: boolean;
|
|
21
|
+
isShared?: boolean;
|
|
22
|
+
createdDateTime?: string;
|
|
23
|
+
lastModifiedDateTime?: string;
|
|
24
|
+
createdBy?: GraphIdentity;
|
|
25
|
+
lastModifiedBy?: GraphIdentity;
|
|
26
|
+
links?: {
|
|
27
|
+
oneNoteClientUrl?: {
|
|
28
|
+
href: string;
|
|
29
|
+
};
|
|
30
|
+
oneNoteWebUrl?: {
|
|
31
|
+
href: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface OneNoteSectionGroup {
|
|
36
|
+
id: string;
|
|
37
|
+
displayName: string;
|
|
38
|
+
/** ID of the notebook this group lives in */
|
|
39
|
+
parentNotebookId?: string;
|
|
40
|
+
/** ID of parent section group (for nested groups) */
|
|
41
|
+
parentSectionGroupId?: string;
|
|
42
|
+
createdDateTime?: string;
|
|
43
|
+
lastModifiedDateTime?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface OneNoteSection {
|
|
46
|
+
id: string;
|
|
47
|
+
displayName: string;
|
|
48
|
+
parentNotebookId?: string;
|
|
49
|
+
parentSectionGroupId?: string;
|
|
50
|
+
createdDateTime?: string;
|
|
51
|
+
lastModifiedDateTime?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface OneNotePage {
|
|
54
|
+
id: string;
|
|
55
|
+
title: string;
|
|
56
|
+
createdDateTime?: string;
|
|
57
|
+
lastModifiedDateTime?: string;
|
|
58
|
+
contentUrl?: string;
|
|
59
|
+
parentSection?: {
|
|
60
|
+
id: string;
|
|
61
|
+
displayName?: string;
|
|
62
|
+
};
|
|
63
|
+
parentNotebook?: {
|
|
64
|
+
id: string;
|
|
65
|
+
displayName?: string;
|
|
66
|
+
};
|
|
67
|
+
createdBy?: GraphIdentity;
|
|
68
|
+
lastModifiedBy?: GraphIdentity;
|
|
69
|
+
links?: {
|
|
70
|
+
oneNoteClientUrl?: {
|
|
71
|
+
href: string;
|
|
72
|
+
};
|
|
73
|
+
oneNoteWebUrl?: {
|
|
74
|
+
href: string;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A page flattened with its full derived kbPath, ready for the fan-out task.
|
|
80
|
+
*/
|
|
81
|
+
export interface DiscoveredOneNotePage {
|
|
82
|
+
onenotePageId: string;
|
|
83
|
+
title: string;
|
|
84
|
+
kbPath: string;
|
|
85
|
+
notebookId: string;
|
|
86
|
+
notebookName: string;
|
|
87
|
+
sectionId: string;
|
|
88
|
+
sectionName: string;
|
|
89
|
+
/** Zero or more nested section groups, outermost first */
|
|
90
|
+
sectionGroups: Array<{
|
|
91
|
+
id: string;
|
|
92
|
+
name: string;
|
|
93
|
+
}>;
|
|
94
|
+
onenoteWebUrl?: string;
|
|
95
|
+
createdDateTime?: string;
|
|
96
|
+
lastModifiedDateTime?: string;
|
|
97
|
+
createdByEmail?: string;
|
|
98
|
+
createdByDisplayName?: string;
|
|
99
|
+
lastModifiedByEmail?: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A cross-page reference captured during HTML conversion, to be resolved
|
|
103
|
+
* into a KB cross-link after all pages are imported.
|
|
104
|
+
*/
|
|
105
|
+
export interface OneNoteExtractedReference {
|
|
106
|
+
sectionIndex: number;
|
|
107
|
+
targetOnenotePageId: string;
|
|
108
|
+
displayText: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Output of the HTML converter: what the fan-out task hands to
|
|
112
|
+
* `create_document` along with per-page cross-references.
|
|
113
|
+
*/
|
|
114
|
+
export interface ConvertedOneNotePage {
|
|
115
|
+
title: string;
|
|
116
|
+
sections: import('../../types.js').SectionInput[];
|
|
117
|
+
extractedReferences: OneNoteExtractedReference[];
|
|
118
|
+
/** Count of images/attachments that were re-hosted */
|
|
119
|
+
mediaCount: number;
|
|
120
|
+
/** Count of content items skipped (unknown MIME, oversized, etc.) */
|
|
121
|
+
skippedItemCount: number;
|
|
122
|
+
/** Human-readable reasons for skipped items, logged back to the job. */
|
|
123
|
+
skippedReasons: string[];
|
|
124
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the OneNote source (Microsoft Graph).
|
|
3
|
+
*
|
|
4
|
+
* These mirror the Graph API payloads we actually read. Graph's own TypeScript
|
|
5
|
+
* types are available via @microsoft/microsoft-graph-types but include every
|
|
6
|
+
* field of every shape; we trim to what the importer touches.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moxn/kb-migrate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.30",
|
|
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",
|
|
@@ -38,6 +38,26 @@
|
|
|
38
38
|
"types": "./dist/sources/local.d.ts",
|
|
39
39
|
"default": "./dist/sources/local.js"
|
|
40
40
|
},
|
|
41
|
+
"./sources/onenote": {
|
|
42
|
+
"types": "./dist/sources/onenote/index.d.ts",
|
|
43
|
+
"default": "./dist/sources/onenote/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./sources/onenote/onenote-api": {
|
|
46
|
+
"types": "./dist/sources/onenote/onenote-api.d.ts",
|
|
47
|
+
"default": "./dist/sources/onenote/onenote-api.js"
|
|
48
|
+
},
|
|
49
|
+
"./sources/onenote/onenote-auth": {
|
|
50
|
+
"types": "./dist/sources/onenote/onenote-auth.d.ts",
|
|
51
|
+
"default": "./dist/sources/onenote/onenote-auth.js"
|
|
52
|
+
},
|
|
53
|
+
"./sources/onenote/onenote-html": {
|
|
54
|
+
"types": "./dist/sources/onenote/onenote-html.d.ts",
|
|
55
|
+
"default": "./dist/sources/onenote/onenote-html.js"
|
|
56
|
+
},
|
|
57
|
+
"./sources/onenote/onenote-tree": {
|
|
58
|
+
"types": "./dist/sources/onenote/onenote-tree.d.ts",
|
|
59
|
+
"default": "./dist/sources/onenote/onenote-tree.js"
|
|
60
|
+
},
|
|
41
61
|
"./sources/base": {
|
|
42
62
|
"types": "./dist/sources/base.d.ts",
|
|
43
63
|
"default": "./dist/sources/base.js"
|
|
@@ -82,11 +102,14 @@
|
|
|
82
102
|
"prepublishOnly": "npm run build"
|
|
83
103
|
},
|
|
84
104
|
"dependencies": {
|
|
105
|
+
"@azure/msal-node": "^3.8.10",
|
|
106
|
+
"@microsoft/microsoft-graph-client": "^3.0.7",
|
|
85
107
|
"@moxn/kb-migrate": "^0.4.14",
|
|
86
108
|
"@notionhq/client": "^5.9.0",
|
|
87
109
|
"@tryfabric/martian": "^1.2.4",
|
|
88
110
|
"commander": "^12.0.0",
|
|
89
111
|
"glob": "^10.0.0",
|
|
112
|
+
"node-html-parser": "^6.1.13",
|
|
90
113
|
"remark-parse": "^11.0.0",
|
|
91
114
|
"unified": "^11.0.0"
|
|
92
115
|
},
|