@braidhq/source-loader-gdrive 0.0.1 → 0.1.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/GoogleDriveLoader.d.ts +43 -45
- package/dist/GoogleDriveLoader.d.ts.map +1 -1
- package/dist/GoogleDriveLoader.js +246 -86
- package/dist/GoogleDriveLoader.js.map +1 -1
- package/dist/Manifest.d.ts +23 -0
- package/dist/Manifest.d.ts.map +1 -0
- package/dist/Manifest.js +22 -0
- package/dist/Manifest.js.map +1 -0
- package/package.json +33 -21
- package/LICENSE +0 -21
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AbsolutePath, LoaderKind, PluginId } from '@braidhq/schema';
|
|
1
|
+
import type { SourceLoaderContext, SourceLoaderPlugin } from '@braidhq/core';
|
|
3
2
|
import { z } from 'zod';
|
|
4
|
-
import { type FetchFn } from './driveClient.js';
|
|
3
|
+
import { type DriveFileMetadata, type FetchFn } from './driveClient.js';
|
|
5
4
|
export declare const GoogleDriveLoaderConfig: z.ZodObject<{
|
|
6
5
|
/**
|
|
7
6
|
* Drive folder id (the long alphanumeric in the URL). Reject the alias
|
|
@@ -12,16 +11,32 @@ export declare const GoogleDriveLoaderConfig: z.ZodObject<{
|
|
|
12
11
|
*/
|
|
13
12
|
folderId: z.ZodEffects<z.ZodString, string, string>;
|
|
14
13
|
/**
|
|
15
|
-
* Whether to
|
|
16
|
-
*
|
|
14
|
+
* Whether to traverse subfolders. Default true. Subfolder hierarchy
|
|
15
|
+
* is NOT preserved on disk; every matching doc lives in its own dir
|
|
16
|
+
* directly under `destination/`. Disable to limit to immediate
|
|
17
|
+
* children of `folderId`.
|
|
17
18
|
*/
|
|
18
19
|
recursive: z.ZodDefault<z.ZodBoolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Optional regex (string). When set, only docs whose *Drive title*
|
|
22
|
+
* matches this pattern are downloaded. Subfolders are still traversed.
|
|
23
|
+
*/
|
|
24
|
+
include: z.ZodOptional<z.ZodString>;
|
|
25
|
+
/**
|
|
26
|
+
* Optional regex (string). When set, docs whose title matches are
|
|
27
|
+
* skipped. Evaluated after `include`, so exclude takes priority.
|
|
28
|
+
*/
|
|
29
|
+
exclude: z.ZodOptional<z.ZodString>;
|
|
19
30
|
}, "strip", z.ZodTypeAny, {
|
|
20
31
|
folderId: string;
|
|
21
32
|
recursive: boolean;
|
|
33
|
+
exclude?: string | undefined;
|
|
34
|
+
include?: string | undefined;
|
|
22
35
|
}, {
|
|
23
36
|
folderId: string;
|
|
37
|
+
exclude?: string | undefined;
|
|
24
38
|
recursive?: boolean | undefined;
|
|
39
|
+
include?: string | undefined;
|
|
25
40
|
}>;
|
|
26
41
|
export type GoogleDriveLoaderConfig = z.infer<typeof GoogleDriveLoaderConfig>;
|
|
27
42
|
export interface GoogleDriveLoaderDeps {
|
|
@@ -35,46 +50,29 @@ export interface GoogleDriveLoaderDeps {
|
|
|
35
50
|
fetchFn?: FetchFn;
|
|
36
51
|
}
|
|
37
52
|
/**
|
|
38
|
-
* Google Drive source loader. Walks a Drive folder, exports
|
|
39
|
-
*
|
|
40
|
-
*
|
|
53
|
+
* Build a Google Drive source loader. Walks a Drive folder, exports every
|
|
54
|
+
* Google Doc inside as markdown, extracts inlined base64 images into
|
|
55
|
+
* sibling files, and lays everything out as:
|
|
41
56
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
57
|
+
* <destination>/
|
|
58
|
+
* <sanitised-doc-title>/
|
|
59
|
+
* index.md
|
|
60
|
+
* <image-label>.png (one per inlined image)
|
|
61
|
+
* <sanitised-doc-title-2>/
|
|
62
|
+
* index.md
|
|
63
|
+
* .braid-manifest.json (sync state)
|
|
64
|
+
*
|
|
65
|
+
* Drive folder hierarchy is NOT preserved on disk; every matched doc
|
|
66
|
+
* is flattened into a sibling directory under `destination/`. Two docs
|
|
67
|
+
* with the same sanitised title will collide; rename one in Drive to
|
|
68
|
+
* disambiguate.
|
|
69
|
+
*
|
|
70
|
+
* Out of scope (intentional): Google Sheets, Slides, Drawings, Forms,
|
|
71
|
+
* and standalone binaries. Mirrors redoc's PRD-focused workflow. If you
|
|
72
|
+
* need spreadsheets / slide decks, ingest them through a different
|
|
73
|
+
* source-loader plugin.
|
|
46
74
|
*/
|
|
47
|
-
export declare
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
readonly type: "source-loader";
|
|
51
|
-
readonly kind: LoaderKind;
|
|
52
|
-
readonly configSchema: z.ZodObject<{
|
|
53
|
-
/**
|
|
54
|
-
* Drive folder id (the long alphanumeric in the URL). Reject the alias
|
|
55
|
-
* `root` outright: it expands to the user's entire My Drive and would
|
|
56
|
-
* mirror every file they own, which is almost never what someone wants
|
|
57
|
-
* from an "intent docs" source and easily costs gigabytes + minutes.
|
|
58
|
-
* Make people pick a specific subfolder.
|
|
59
|
-
*/
|
|
60
|
-
folderId: z.ZodEffects<z.ZodString, string, string>;
|
|
61
|
-
/**
|
|
62
|
-
* Whether to follow subfolders recursively. Default true. Disable for
|
|
63
|
-
* flat folder mirrors where nested folders should be skipped.
|
|
64
|
-
*/
|
|
65
|
-
recursive: z.ZodDefault<z.ZodBoolean>;
|
|
66
|
-
}, "strip", z.ZodTypeAny, {
|
|
67
|
-
folderId: string;
|
|
68
|
-
recursive: boolean;
|
|
69
|
-
}, {
|
|
70
|
-
folderId: string;
|
|
71
|
-
recursive?: boolean | undefined;
|
|
72
|
-
}>;
|
|
73
|
-
constructor(deps: GoogleDriveLoaderDeps);
|
|
74
|
-
ingest(rawConfig: unknown, destination: AbsolutePath, context: SourceLoaderContext): Promise<IngestReport>;
|
|
75
|
-
sync(rawConfig: unknown, destination: AbsolutePath, context: SourceLoaderContext): Promise<SyncReport>;
|
|
76
|
-
private client;
|
|
77
|
-
private walk;
|
|
78
|
-
private writeOneFile;
|
|
79
|
-
}
|
|
75
|
+
export declare function createGoogleDriveLoader(deps: GoogleDriveLoaderDeps): SourceLoaderPlugin;
|
|
76
|
+
/** Note: the helper used by DriveClient.exportDoc returns Buffer. */
|
|
77
|
+
export type { DriveFileMetadata, FetchFn };
|
|
80
78
|
//# sourceMappingURL=GoogleDriveLoader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleDriveLoader.d.ts","sourceRoot":"","sources":["../src/GoogleDriveLoader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"GoogleDriveLoader.d.ts","sourceRoot":"","sources":["../src/GoogleDriveLoader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAK5E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAe,KAAK,iBAAiB,EAAE,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAA;AA2BpF,eAAO,MAAM,uBAAuB;IAClC;;;;;;OAMG;;IAMH;;;;;OAKG;;IAEH;;;OAGG;;IAEH;;;OAGG;;;;;;;;;;;;EAEH,CAAA;AACF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAE7E,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACrE,2DAA2D;IAC3D,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAaD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,qBAAqB,GAAG,kBAAkB,CAgHvF;AA8GD,qEAAqE;AACrE,YAAY,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAA"}
|
|
@@ -1,20 +1,31 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
1
2
|
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
4
|
+
import { defineSourceLoader } from '@braidhq/sdk';
|
|
3
5
|
import { z } from 'zod';
|
|
4
6
|
import { DriveClient } from './driveClient.js';
|
|
7
|
+
import { readManifest, writeManifest } from './Manifest.js';
|
|
5
8
|
const FOLDER_MIME = 'application/vnd.google-apps.folder';
|
|
9
|
+
const DOC_MIME = 'application/vnd.google-apps.document';
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
+
* Drive auto-creates duplicates named "Copy of …" (English) or "…的副本"
|
|
12
|
+
* (Chinese) when users use the Make a Copy menu. They almost always carry
|
|
13
|
+
* stale content that would conflict with the canonical doc, so we skip
|
|
14
|
+
* them silently. Same rule as redoc.
|
|
11
15
|
*/
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const COPY_PATTERNS = [/^Copy of /, /的副本$/];
|
|
17
|
+
/**
|
|
18
|
+
* Drive's `text/markdown` export inlines images as base64 data URIs in
|
|
19
|
+
* reference-style link form:
|
|
20
|
+
*
|
|
21
|
+
* [image0]: <data:image/png;base64,iVBORw0KG…>
|
|
22
|
+
*
|
|
23
|
+
* Leaving those base64 blobs in the markdown bloats the file by ~33 % per
|
|
24
|
+
* image and is unreadable for both humans and LLMs. This regex matches one
|
|
25
|
+
* such reference line so we can extract the image to its own file and
|
|
26
|
+
* rewrite the link to a relative path. Same approach as redoc.
|
|
27
|
+
*/
|
|
28
|
+
const INLINE_IMAGE_RE = /^\[([^\]]+)\]:\s*<data:image\/([\w+]+);base64,([^>]+)>$/gm;
|
|
18
29
|
export const GoogleDriveLoaderConfig = z.object({
|
|
19
30
|
/**
|
|
20
31
|
* Drive folder id (the long alphanumeric in the URL). Reject the alias
|
|
@@ -29,97 +40,246 @@ export const GoogleDriveLoaderConfig = z.object({
|
|
|
29
40
|
message: 'folderId "root" refers to the entire My Drive and is rejected. Create a dedicated subfolder and use its id instead.',
|
|
30
41
|
}),
|
|
31
42
|
/**
|
|
32
|
-
* Whether to
|
|
33
|
-
*
|
|
43
|
+
* Whether to traverse subfolders. Default true. Subfolder hierarchy
|
|
44
|
+
* is NOT preserved on disk; every matching doc lives in its own dir
|
|
45
|
+
* directly under `destination/`. Disable to limit to immediate
|
|
46
|
+
* children of `folderId`.
|
|
34
47
|
*/
|
|
35
48
|
recursive: z.boolean().default(true),
|
|
49
|
+
/**
|
|
50
|
+
* Optional regex (string). When set, only docs whose *Drive title*
|
|
51
|
+
* matches this pattern are downloaded. Subfolders are still traversed.
|
|
52
|
+
*/
|
|
53
|
+
include: z.string().optional(),
|
|
54
|
+
/**
|
|
55
|
+
* Optional regex (string). When set, docs whose title matches are
|
|
56
|
+
* skipped. Evaluated after `include`, so exclude takes priority.
|
|
57
|
+
*/
|
|
58
|
+
exclude: z.string().optional(),
|
|
36
59
|
});
|
|
37
60
|
/**
|
|
38
|
-
* Google Drive source loader. Walks a Drive folder, exports
|
|
39
|
-
*
|
|
40
|
-
*
|
|
61
|
+
* Build a Google Drive source loader. Walks a Drive folder, exports every
|
|
62
|
+
* Google Doc inside as markdown, extracts inlined base64 images into
|
|
63
|
+
* sibling files, and lays everything out as:
|
|
64
|
+
*
|
|
65
|
+
* <destination>/
|
|
66
|
+
* <sanitised-doc-title>/
|
|
67
|
+
* index.md
|
|
68
|
+
* <image-label>.png (one per inlined image)
|
|
69
|
+
* <sanitised-doc-title-2>/
|
|
70
|
+
* index.md
|
|
71
|
+
* .braid-manifest.json (sync state)
|
|
41
72
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
73
|
+
* Drive folder hierarchy is NOT preserved on disk; every matched doc
|
|
74
|
+
* is flattened into a sibling directory under `destination/`. Two docs
|
|
75
|
+
* with the same sanitised title will collide; rename one in Drive to
|
|
76
|
+
* disambiguate.
|
|
77
|
+
*
|
|
78
|
+
* Out of scope (intentional): Google Sheets, Slides, Drawings, Forms,
|
|
79
|
+
* and standalone binaries. Mirrors redoc's PRD-focused workflow. If you
|
|
80
|
+
* need spreadsheets / slide decks, ingest them through a different
|
|
81
|
+
* source-loader plugin.
|
|
46
82
|
*/
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
export function createGoogleDriveLoader(deps) {
|
|
84
|
+
return defineSourceLoader({
|
|
85
|
+
kind: 'gdrive',
|
|
86
|
+
configSchema: GoogleDriveLoaderConfig,
|
|
87
|
+
ingest: async (config, destination, context) => {
|
|
88
|
+
await rm(destination, { recursive: true, force: true });
|
|
89
|
+
await mkdir(destination, { recursive: true });
|
|
90
|
+
const client = await buildClient(deps, context);
|
|
91
|
+
const candidates = await walk(client, config);
|
|
92
|
+
const manifest = {
|
|
93
|
+
folderId: config.folderId,
|
|
94
|
+
include: config.include,
|
|
95
|
+
exclude: config.exclude,
|
|
96
|
+
files: {},
|
|
97
|
+
};
|
|
98
|
+
for (const doc of candidates) {
|
|
99
|
+
await downloadOne(client, doc, destination);
|
|
100
|
+
manifest.files[doc.id] = entryOf(doc);
|
|
101
|
+
}
|
|
102
|
+
await writeManifest(destination, manifest);
|
|
103
|
+
return {
|
|
104
|
+
localPath: destination,
|
|
105
|
+
metadata: { folderId: config.folderId, fileCount: candidates.length },
|
|
106
|
+
fetchedAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
sync: async (config, destination, context) => {
|
|
110
|
+
const cached = await readManifest(destination);
|
|
111
|
+
if (!cached) {
|
|
112
|
+
// No manifest yet (first sync after upgrade / cache wiped). Fall
|
|
113
|
+
// back to a clean ingest so we end up in a known-good state.
|
|
114
|
+
const client = await buildClient(deps, context);
|
|
115
|
+
await rm(destination, { recursive: true, force: true });
|
|
116
|
+
await mkdir(destination, { recursive: true });
|
|
117
|
+
const candidates = await walk(client, config);
|
|
118
|
+
const manifest = {
|
|
119
|
+
folderId: config.folderId,
|
|
120
|
+
include: config.include,
|
|
121
|
+
exclude: config.exclude,
|
|
122
|
+
files: {},
|
|
123
|
+
};
|
|
124
|
+
for (const doc of candidates) {
|
|
125
|
+
await downloadOne(client, doc, destination);
|
|
126
|
+
manifest.files[doc.id] = entryOf(doc);
|
|
127
|
+
}
|
|
128
|
+
await writeManifest(destination, manifest);
|
|
129
|
+
return {
|
|
130
|
+
changed: true,
|
|
131
|
+
added: candidates.length,
|
|
132
|
+
updated: 0,
|
|
133
|
+
removed: 0,
|
|
134
|
+
unchanged: 0,
|
|
135
|
+
metadata: { folderId: config.folderId, fileCount: candidates.length },
|
|
136
|
+
fetchedAt: new Date().toISOString(),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const client = await buildClient(deps, context);
|
|
140
|
+
const candidates = await walk(client, config);
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
let added = 0;
|
|
143
|
+
let updated = 0;
|
|
144
|
+
let unchanged = 0;
|
|
145
|
+
const next = {
|
|
146
|
+
folderId: config.folderId,
|
|
147
|
+
include: config.include,
|
|
148
|
+
exclude: config.exclude,
|
|
149
|
+
files: {},
|
|
150
|
+
};
|
|
151
|
+
for (const doc of candidates) {
|
|
152
|
+
seen.add(doc.id);
|
|
153
|
+
const prior = cached.files[doc.id];
|
|
154
|
+
if (!prior) {
|
|
155
|
+
await downloadOne(client, doc, destination);
|
|
156
|
+
added++;
|
|
157
|
+
}
|
|
158
|
+
else if (prior.localDir !== doc.localDir || prior.modifiedTime !== doc.modifiedTime) {
|
|
159
|
+
// Either content updated or the doc was renamed in Drive. Both
|
|
160
|
+
// cases: re-download to the new dir, then rm the old dir.
|
|
161
|
+
if (prior.localDir !== doc.localDir)
|
|
162
|
+
await rm(join(destination, prior.localDir), { recursive: true, force: true });
|
|
163
|
+
await downloadOne(client, doc, destination);
|
|
164
|
+
updated++;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
unchanged++;
|
|
168
|
+
}
|
|
169
|
+
next.files[doc.id] = entryOf(doc);
|
|
170
|
+
}
|
|
171
|
+
let removed = 0;
|
|
172
|
+
for (const [id, entry] of Object.entries(cached.files)) {
|
|
173
|
+
if (seen.has(id))
|
|
174
|
+
continue;
|
|
175
|
+
await rm(join(destination, entry.localDir), { recursive: true, force: true });
|
|
176
|
+
removed++;
|
|
177
|
+
}
|
|
178
|
+
await writeManifest(destination, next);
|
|
179
|
+
return {
|
|
180
|
+
changed: added + updated + removed > 0,
|
|
181
|
+
added,
|
|
182
|
+
updated,
|
|
183
|
+
removed,
|
|
184
|
+
unchanged,
|
|
185
|
+
metadata: { folderId: config.folderId },
|
|
186
|
+
fetchedAt: new Date().toISOString(),
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async function buildClient(deps, context) {
|
|
192
|
+
const token = await deps.resolveAccessToken(context);
|
|
193
|
+
return new DriveClient(token, deps.fetchFn);
|
|
194
|
+
}
|
|
195
|
+
async function walk(client, config) {
|
|
196
|
+
const includeRe = compileRegex(config.include, 'include');
|
|
197
|
+
const excludeRe = compileRegex(config.exclude, 'exclude');
|
|
198
|
+
const out = [];
|
|
199
|
+
const seenDirs = new Map(); // localDir -> first doc id, to detect collisions
|
|
200
|
+
const visit = async (folderId) => {
|
|
86
201
|
const children = await client.listChildren(folderId);
|
|
87
202
|
for (const child of children) {
|
|
88
203
|
if (child.mimeType === FOLDER_MIME) {
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
const subDest = join(destination, sanitiseName(child.name));
|
|
92
|
-
await mkdir(subDest, { recursive: true });
|
|
93
|
-
const nested = await this.walk(client, child.id, subDest, options);
|
|
94
|
-
fileCount += nested.fileCount;
|
|
204
|
+
if (config.recursive)
|
|
205
|
+
await visit(child.id);
|
|
95
206
|
continue;
|
|
96
207
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
208
|
+
if (child.mimeType !== DOC_MIME)
|
|
209
|
+
continue;
|
|
210
|
+
if (COPY_PATTERNS.some(re => re.test(child.name)))
|
|
211
|
+
continue;
|
|
212
|
+
if (includeRe && !includeRe.test(child.name))
|
|
213
|
+
continue;
|
|
214
|
+
if (excludeRe && excludeRe.test(child.name))
|
|
215
|
+
continue;
|
|
216
|
+
const localDir = sanitiseName(child.name);
|
|
217
|
+
const collides = seenDirs.get(localDir);
|
|
218
|
+
if (collides && collides !== child.id) {
|
|
219
|
+
// Two distinct Drive docs sanitise to the same dir name. Keep
|
|
220
|
+
// the first one we walked into; skipping the second is safer
|
|
221
|
+
// than silently overwriting. User can rename in Drive.
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
seenDirs.set(localDir, child.id);
|
|
225
|
+
out.push({
|
|
226
|
+
id: child.id,
|
|
227
|
+
title: child.name,
|
|
228
|
+
modifiedTime: child.modifiedTime,
|
|
229
|
+
localDir,
|
|
230
|
+
});
|
|
100
231
|
}
|
|
101
|
-
|
|
232
|
+
};
|
|
233
|
+
await visit(config.folderId);
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
async function downloadOne(client, doc, destination) {
|
|
237
|
+
const docDir = join(destination, doc.localDir);
|
|
238
|
+
// Clean any prior content for this doc so removed images don't linger.
|
|
239
|
+
await rm(docDir, { recursive: true, force: true });
|
|
240
|
+
await mkdir(docDir, { recursive: true });
|
|
241
|
+
const bytes = await client.exportDoc(doc.id, 'text/markdown');
|
|
242
|
+
const markdown = bytes.toString('utf-8');
|
|
243
|
+
const rewritten = await extractInlineImages(markdown, docDir);
|
|
244
|
+
await writeFile(join(docDir, 'index.md'), rewritten, 'utf-8');
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Pull every `[label]: <data:image/ext;base64,...>` reference out of the
|
|
248
|
+
* markdown, write the decoded bytes as `<label>.<ext>` in `docDir`, and
|
|
249
|
+
* rewrite the reference to point at the new local file. Returns the
|
|
250
|
+
* cleaned markdown. Matches redoc's behaviour.
|
|
251
|
+
*/
|
|
252
|
+
async function extractInlineImages(markdown, docDir) {
|
|
253
|
+
const writes = [];
|
|
254
|
+
const rewritten = markdown.replace(INLINE_IMAGE_RE, (_match, label, ext, data) => {
|
|
255
|
+
const filename = `${sanitiseName(label)}.${ext}`;
|
|
256
|
+
writes.push(writeFile(join(docDir, filename), Buffer.from(data, 'base64')));
|
|
257
|
+
return `[${label}]: <${filename}>`;
|
|
258
|
+
});
|
|
259
|
+
await Promise.all(writes);
|
|
260
|
+
return rewritten;
|
|
261
|
+
}
|
|
262
|
+
function compileRegex(pattern, field) {
|
|
263
|
+
if (!pattern)
|
|
264
|
+
return undefined;
|
|
265
|
+
try {
|
|
266
|
+
return new RegExp(pattern);
|
|
102
267
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (exportSpec) {
|
|
106
|
-
const bytes = await client.exportDoc(file.id, exportSpec.mimeType);
|
|
107
|
-
const filename = `${sanitiseName(file.name)}${exportSpec.extension}`;
|
|
108
|
-
await writeFile(join(destination, filename), bytes);
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
// Skip native Drive types we don't have an export for (e.g. forms).
|
|
112
|
-
if (file.mimeType.startsWith('application/vnd.google-apps.'))
|
|
113
|
-
return false;
|
|
114
|
-
// Regular binary: download as-is, preserve the name.
|
|
115
|
-
const bytes = await client.downloadFile(file.id);
|
|
116
|
-
await writeFile(join(destination, sanitiseName(file.name)), bytes);
|
|
117
|
-
return true;
|
|
268
|
+
catch (err) {
|
|
269
|
+
throw new Error(`googleDriveLoader: ${field} is not a valid regex (${err.message})`);
|
|
118
270
|
}
|
|
119
271
|
}
|
|
272
|
+
function entryOf(doc) {
|
|
273
|
+
return {
|
|
274
|
+
localDir: doc.localDir,
|
|
275
|
+
modifiedTime: doc.modifiedTime,
|
|
276
|
+
title: doc.title,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
120
279
|
function sanitiseName(name) {
|
|
121
|
-
// Replace path separators
|
|
122
|
-
// human-readable
|
|
123
|
-
|
|
280
|
+
// Replace path separators, control chars, and common shell-hostile
|
|
281
|
+
// characters with `_`. Spaces stay so human-readable titles survive
|
|
282
|
+
// ("Roadmap Q3 2026" → "Roadmap Q3 2026").
|
|
283
|
+
return name.replace(/[/\\:*?"<>|]/g, '_');
|
|
124
284
|
}
|
|
125
285
|
//# sourceMappingURL=GoogleDriveLoader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleDriveLoader.js","sourceRoot":"","sources":["../src/GoogleDriveLoader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GoogleDriveLoader.js","sourceRoot":"","sources":["../src/GoogleDriveLoader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Filename written inside `destination` to track per-file sync state. */
|
|
2
|
+
export declare const MANIFEST_FILENAME = ".braid-manifest.json";
|
|
3
|
+
export interface ManifestEntry {
|
|
4
|
+
/** Posix relative dir under `destination`: the per-doc package. */
|
|
5
|
+
localDir: string;
|
|
6
|
+
modifiedTime: string;
|
|
7
|
+
/** Drive title at the time of the last sync, for debugging only. */
|
|
8
|
+
title: string;
|
|
9
|
+
}
|
|
10
|
+
export interface Manifest {
|
|
11
|
+
folderId: string;
|
|
12
|
+
include: string | undefined;
|
|
13
|
+
exclude: string | undefined;
|
|
14
|
+
files: Record<string, ManifestEntry>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read the manifest file from `destination`. Returns `undefined` if the
|
|
18
|
+
* file is missing or unparseable; callers should fall back to a clean
|
|
19
|
+
* ingest in that case (no manifest = no incremental state to diff against).
|
|
20
|
+
*/
|
|
21
|
+
export declare function readManifest(destination: string): Promise<Manifest | undefined>;
|
|
22
|
+
export declare function writeManifest(destination: string, manifest: Manifest): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=Manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Manifest.d.ts","sourceRoot":"","sources":["../src/Manifest.ts"],"names":[],"mappings":"AAGA,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,yBAAyB,CAAA;AAEvD,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;CACrC;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAQrF;AAED,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAM1F"}
|
package/dist/Manifest.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/** Filename written inside `destination` to track per-file sync state. */
|
|
4
|
+
export const MANIFEST_FILENAME = '.braid-manifest.json';
|
|
5
|
+
/**
|
|
6
|
+
* Read the manifest file from `destination`. Returns `undefined` if the
|
|
7
|
+
* file is missing or unparseable; callers should fall back to a clean
|
|
8
|
+
* ingest in that case (no manifest = no incremental state to diff against).
|
|
9
|
+
*/
|
|
10
|
+
export async function readManifest(destination) {
|
|
11
|
+
try {
|
|
12
|
+
const raw = await readFile(join(destination, MANIFEST_FILENAME), 'utf-8');
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function writeManifest(destination, manifest) {
|
|
20
|
+
await writeFile(join(destination, MANIFEST_FILENAME), `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=Manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Manifest.js","sourceRoot":"","sources":["../src/Manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAA;AAiBvD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,WAAmB;IACpD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,CAAA;QACzE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAa,CAAA;IACpC,CAAC;IACD,MAAM,CAAC;QACL,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,WAAmB,EAAE,QAAkB;IACzE,MAAM,SAAS,CACb,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,EACpC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EACxC,OAAO,CACR,CAAA;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,37 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braidhq/source-loader-gdrive",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0
|
|
5
|
-
"description": "Google Drive source-loader plugin
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Google Drive source-loader plugin for Braid. Export docs from Google Drive, requires OAuth on first use.",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mroops0111/braid.git",
|
|
10
|
+
"directory": "packages/source-loader-gdrive"
|
|
11
|
+
},
|
|
7
12
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
"types": "./dist/index.d.ts",
|
|
10
|
-
"default": "./dist/index.js"
|
|
11
|
-
}
|
|
13
|
+
".": "./src/index.ts"
|
|
12
14
|
},
|
|
13
|
-
"main": "./
|
|
14
|
-
"types": "./
|
|
15
|
+
"main": "./src/index.ts",
|
|
16
|
+
"types": "./src/index.ts",
|
|
15
17
|
"files": [
|
|
16
18
|
"README.md",
|
|
17
19
|
"dist"
|
|
18
20
|
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.json",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"clean": "rm -rf dist .turbo *.tsbuildinfo coverage"
|
|
26
|
+
},
|
|
19
27
|
"dependencies": {
|
|
20
|
-
"
|
|
21
|
-
"@braidhq/
|
|
22
|
-
"@braidhq/
|
|
28
|
+
"@braidhq/core": "workspace:*",
|
|
29
|
+
"@braidhq/schema": "workspace:*",
|
|
30
|
+
"@braidhq/sdk": "workspace:*",
|
|
31
|
+
"zod": "^3.24.0"
|
|
23
32
|
},
|
|
24
33
|
"devDependencies": {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
34
|
+
"@braidhq/test-utils": "workspace:*",
|
|
35
|
+
"typescript": "^6.0.3",
|
|
36
|
+
"vitest": "^4.1.0"
|
|
27
37
|
},
|
|
28
38
|
"publishConfig": {
|
|
29
|
-
"access": "public"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
"access": "public",
|
|
40
|
+
"main": "./dist/index.js",
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"default": "./dist/index.js"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
36
48
|
}
|
|
37
|
-
}
|
|
49
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 mroops0111
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|