mdorigin 0.1.8 → 0.2.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/README.md +12 -0
- package/dist/adapters/cloudflare.d.ts +40 -4
- package/dist/adapters/cloudflare.js +163 -7
- package/dist/adapters/node.js +2 -2
- package/dist/cli/build-cloudflare.js +27 -2
- package/dist/cli/help.js +5 -4
- package/dist/cli/init-cloudflare.js +7 -1
- package/dist/cli/main.js +5 -0
- package/dist/cli/sync-cloudflare-r2.d.ts +1 -0
- package/dist/cli/sync-cloudflare-r2.js +47 -0
- package/dist/cloudflare-runtime.d.ts +1 -1
- package/dist/cloudflare.d.ts +42 -6
- package/dist/cloudflare.js +246 -10
- package/dist/core/content-store.d.ts +1 -0
- package/dist/core/content-store.js +8 -1
- package/dist/core/extensions.d.ts +5 -9
- package/dist/core/markdown.js +6 -2
- package/dist/core/request-handler.js +46 -60
- package/dist/core/site-config.d.ts +4 -10
- package/dist/core/site-config.js +18 -10
- package/dist/html/template.d.ts +5 -9
- package/dist/html/template.js +25 -25
- package/dist/html/theme.d.ts +1 -2
- package/dist/html/theme.js +2 -642
- package/dist/index-builder.js +5 -4
- package/dist/search.js +3 -2
- package/package.json +4 -2
- package/dist/html/template-kind.d.ts +0 -1
- package/dist/html/template-kind.js +0 -1
package/README.md
CHANGED
|
@@ -108,6 +108,18 @@ mdorigin dev --root docs/site --search dist/search
|
|
|
108
108
|
mdorigin build cloudflare --root docs/site --search dist/search
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
For media-heavy sites that would exceed Worker bundle limits, build in external binary mode instead:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
mdorigin build cloudflare --root docs/site --binary-mode external
|
|
115
|
+
mdorigin sync cloudflare-r2 --dir dist/cloudflare --bucket <bucket-name>
|
|
116
|
+
mdorigin init cloudflare --dir . --r2-bucket <bucket-name>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
In this mode, markdown and other text stay embedded in the Worker bundle, smaller binaries are staged for Workers Static Assets, and oversized binaries are staged for R2.
|
|
120
|
+
|
|
121
|
+
`mdorigin` ignores dotfiles and dot-directories during content traversal. `.gitignore` is not used to decide publish visibility.
|
|
122
|
+
|
|
111
123
|
Runtime endpoints:
|
|
112
124
|
|
|
113
125
|
- `/api/search?q=cloudflare+deploy`
|
|
@@ -1,22 +1,58 @@
|
|
|
1
|
-
import { type ContentEntryKind } from '../core/content-store.js';
|
|
2
1
|
import type { MdoPlugin } from '../core/extensions.js';
|
|
3
2
|
import type { ResolvedSiteConfig } from '../core/site-config.js';
|
|
4
3
|
import { type SearchBundleEntry } from '../search.js';
|
|
5
|
-
export interface
|
|
4
|
+
export interface TextCloudflareManifestEntry {
|
|
6
5
|
path: string;
|
|
7
|
-
kind:
|
|
6
|
+
kind: 'text';
|
|
8
7
|
mediaType: string;
|
|
9
8
|
text?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface InlineBinaryCloudflareManifestEntry {
|
|
11
|
+
path: string;
|
|
12
|
+
kind: 'binary';
|
|
13
|
+
mediaType: string;
|
|
10
14
|
base64?: string;
|
|
11
15
|
}
|
|
16
|
+
export interface ExternalBinaryCloudflareManifestEntry {
|
|
17
|
+
path: string;
|
|
18
|
+
kind: 'binary';
|
|
19
|
+
mediaType: string;
|
|
20
|
+
storageKind: 'assets' | 'r2';
|
|
21
|
+
storageKey: string;
|
|
22
|
+
byteSize: number;
|
|
23
|
+
}
|
|
24
|
+
export type CloudflareManifestEntry = TextCloudflareManifestEntry | InlineBinaryCloudflareManifestEntry | ExternalBinaryCloudflareManifestEntry;
|
|
25
|
+
export interface CloudflareBundleRuntimeConfig {
|
|
26
|
+
binaryMode: 'inline' | 'external';
|
|
27
|
+
r2Binding?: string;
|
|
28
|
+
}
|
|
12
29
|
export interface CloudflareManifest {
|
|
13
30
|
entries: CloudflareManifestEntry[];
|
|
14
31
|
siteConfig?: ResolvedSiteConfig;
|
|
15
32
|
searchEntries?: SearchBundleEntry[];
|
|
33
|
+
runtime?: CloudflareBundleRuntimeConfig;
|
|
16
34
|
}
|
|
17
|
-
export interface
|
|
35
|
+
export interface CloudflareAssetsBindingLike {
|
|
18
36
|
fetch(request: Request): Promise<Response>;
|
|
19
37
|
}
|
|
38
|
+
export interface CloudflareR2ObjectBodyLike {
|
|
39
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
40
|
+
}
|
|
41
|
+
export interface CloudflareR2ObjectLike {
|
|
42
|
+
body: CloudflareR2ObjectBodyLike | ReadableStream | null;
|
|
43
|
+
arrayBuffer?: () => Promise<ArrayBuffer>;
|
|
44
|
+
httpEtag?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface CloudflareR2BucketLike {
|
|
47
|
+
get(key: string): Promise<CloudflareR2ObjectLike | null>;
|
|
48
|
+
}
|
|
49
|
+
export interface CloudflareWorkerEnv {
|
|
50
|
+
ASSETS?: CloudflareAssetsBindingLike;
|
|
51
|
+
[binding: string]: unknown;
|
|
52
|
+
}
|
|
53
|
+
export interface ExportedHandlerLike {
|
|
54
|
+
fetch(request: Request, env?: CloudflareWorkerEnv, ctx?: unknown): Promise<Response>;
|
|
55
|
+
}
|
|
20
56
|
export interface CreateCloudflareWorkerOptions {
|
|
21
57
|
plugins?: MdoPlugin[];
|
|
22
58
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { MemoryContentStore
|
|
1
|
+
import { MemoryContentStore } from '../core/content-store.js';
|
|
2
2
|
import { handleSiteRequest } from '../core/request-handler.js';
|
|
3
|
+
import { resolveRequest } from '../core/router.js';
|
|
3
4
|
import { createSearchApiFromBundle } from '../search.js';
|
|
4
5
|
export function createCloudflareWorker(manifest, options = {}) {
|
|
5
|
-
const
|
|
6
|
+
const storeIndex = new MemoryContentStore(manifest.entries.map((entry) => {
|
|
6
7
|
if (entry.kind === 'text') {
|
|
7
8
|
return {
|
|
8
9
|
path: entry.path,
|
|
@@ -11,6 +12,13 @@ export function createCloudflareWorker(manifest, options = {}) {
|
|
|
11
12
|
text: entry.text ?? '',
|
|
12
13
|
};
|
|
13
14
|
}
|
|
15
|
+
if ('storageKind' in entry) {
|
|
16
|
+
return {
|
|
17
|
+
path: entry.path,
|
|
18
|
+
kind: 'binary',
|
|
19
|
+
mediaType: entry.mediaType,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
14
22
|
return {
|
|
15
23
|
path: entry.path,
|
|
16
24
|
kind: 'binary',
|
|
@@ -22,8 +30,13 @@ export function createCloudflareWorker(manifest, options = {}) {
|
|
|
22
30
|
? createSearchApiFromBundle(manifest.searchEntries)
|
|
23
31
|
: undefined;
|
|
24
32
|
return {
|
|
25
|
-
async fetch(request) {
|
|
33
|
+
async fetch(request, env) {
|
|
26
34
|
const url = new URL(request.url);
|
|
35
|
+
const directBinaryResponse = await tryServeExternalBinary(manifest, request, env);
|
|
36
|
+
if (directBinaryResponse !== null) {
|
|
37
|
+
return directBinaryResponse;
|
|
38
|
+
}
|
|
39
|
+
const store = new CloudflareManifestContentStore(manifest, storeIndex, request, env);
|
|
27
40
|
const siteResponse = await handleSiteRequest(store, url.pathname, {
|
|
28
41
|
draftMode: 'exclude',
|
|
29
42
|
siteConfig: manifest.siteConfig ?? {
|
|
@@ -33,16 +46,14 @@ export function createCloudflareWorker(manifest, options = {}) {
|
|
|
33
46
|
logo: undefined,
|
|
34
47
|
showDate: true,
|
|
35
48
|
showSummary: true,
|
|
36
|
-
theme: 'paper',
|
|
37
|
-
template: 'document',
|
|
38
49
|
topNav: [],
|
|
39
50
|
footerNav: [],
|
|
40
51
|
footerText: undefined,
|
|
41
52
|
socialLinks: [],
|
|
42
53
|
editLink: undefined,
|
|
43
54
|
showHomeIndex: true,
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
listingInitialPostCount: 10,
|
|
56
|
+
listingLoadMoreStep: 10,
|
|
46
57
|
siteTitleConfigured: false,
|
|
47
58
|
siteDescriptionConfigured: false,
|
|
48
59
|
},
|
|
@@ -65,6 +76,151 @@ export function createCloudflareWorker(manifest, options = {}) {
|
|
|
65
76
|
},
|
|
66
77
|
};
|
|
67
78
|
}
|
|
79
|
+
async function tryServeExternalBinary(manifest, request, env) {
|
|
80
|
+
const resolved = resolveRequest(new URL(request.url).pathname);
|
|
81
|
+
if (resolved.kind !== 'asset' || !resolved.sourcePath) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const manifestEntry = manifest.entries.find((entry) => entry.path === resolved.sourcePath && isExternalBinaryEntry(entry));
|
|
85
|
+
if (!manifestEntry) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (manifestEntry.storageKind === 'assets') {
|
|
89
|
+
const assetsBinding = env?.ASSETS;
|
|
90
|
+
if (!assetsBinding) {
|
|
91
|
+
throw new Error(`Cloudflare ASSETS binding is required to serve ${manifestEntry.path}.`);
|
|
92
|
+
}
|
|
93
|
+
const assetUrl = new URL(request.url);
|
|
94
|
+
assetUrl.pathname = `/${manifestEntry.storageKey}`;
|
|
95
|
+
return assetsBinding.fetch(new Request(assetUrl.toString(), request));
|
|
96
|
+
}
|
|
97
|
+
const bucket = env?.[manifest.runtime?.r2Binding ?? 'MDORIGIN_R2'];
|
|
98
|
+
if (!bucket) {
|
|
99
|
+
throw new Error(`Cloudflare R2 binding ${manifest.runtime?.r2Binding ?? 'MDORIGIN_R2'} is required to serve ${manifestEntry.path}.`);
|
|
100
|
+
}
|
|
101
|
+
const object = await bucket.get(manifestEntry.storageKey);
|
|
102
|
+
if (!object) {
|
|
103
|
+
return new Response('Not Found', {
|
|
104
|
+
status: 404,
|
|
105
|
+
headers: {
|
|
106
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const headers = new Headers({
|
|
111
|
+
'content-type': manifestEntry.mediaType,
|
|
112
|
+
});
|
|
113
|
+
if (object.httpEtag) {
|
|
114
|
+
headers.set('etag', object.httpEtag);
|
|
115
|
+
}
|
|
116
|
+
if (request.method === 'HEAD') {
|
|
117
|
+
return new Response(null, {
|
|
118
|
+
status: 200,
|
|
119
|
+
headers,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (object.body instanceof ReadableStream) {
|
|
123
|
+
return new Response(object.body, {
|
|
124
|
+
status: 200,
|
|
125
|
+
headers,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (object.body && 'arrayBuffer' in object.body) {
|
|
129
|
+
return new Response(await object.body.arrayBuffer(), {
|
|
130
|
+
status: 200,
|
|
131
|
+
headers,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (typeof object.arrayBuffer === 'function') {
|
|
135
|
+
return new Response(await object.arrayBuffer(), {
|
|
136
|
+
status: 200,
|
|
137
|
+
headers,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return new Response(null, {
|
|
141
|
+
status: 200,
|
|
142
|
+
headers,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function isExternalBinaryEntry(entry) {
|
|
146
|
+
return entry.kind === 'binary' && 'storageKind' in entry;
|
|
147
|
+
}
|
|
148
|
+
class CloudflareManifestContentStore {
|
|
149
|
+
storeIndex;
|
|
150
|
+
request;
|
|
151
|
+
env;
|
|
152
|
+
entries;
|
|
153
|
+
runtime;
|
|
154
|
+
constructor(manifest, storeIndex, request, env) {
|
|
155
|
+
this.storeIndex = storeIndex;
|
|
156
|
+
this.request = request;
|
|
157
|
+
this.env = env;
|
|
158
|
+
this.entries = new Map(manifest.entries.map((entry) => [entry.path, entry]));
|
|
159
|
+
this.runtime = manifest.runtime;
|
|
160
|
+
}
|
|
161
|
+
async get(contentPath) {
|
|
162
|
+
const baseEntry = await this.storeIndex.get(contentPath);
|
|
163
|
+
if (baseEntry === null) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const manifestEntry = this.entries.get(contentPath);
|
|
167
|
+
if (!manifestEntry || manifestEntry.kind === 'text') {
|
|
168
|
+
return baseEntry;
|
|
169
|
+
}
|
|
170
|
+
if (!('storageKind' in manifestEntry)) {
|
|
171
|
+
return baseEntry;
|
|
172
|
+
}
|
|
173
|
+
if (manifestEntry.storageKind === 'assets') {
|
|
174
|
+
const assetsBinding = this.env?.ASSETS;
|
|
175
|
+
if (!assetsBinding) {
|
|
176
|
+
throw new Error(`Cloudflare ASSETS binding is required to serve ${manifestEntry.path}.`);
|
|
177
|
+
}
|
|
178
|
+
const assetResponse = await assetsBinding.fetch(new Request(new URL(`/${manifestEntry.storageKey}`, this.request.url), {
|
|
179
|
+
method: 'GET',
|
|
180
|
+
}));
|
|
181
|
+
if (!assetResponse.ok) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
path: manifestEntry.path,
|
|
186
|
+
kind: 'binary',
|
|
187
|
+
mediaType: manifestEntry.mediaType,
|
|
188
|
+
bytes: new Uint8Array(await assetResponse.arrayBuffer()),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const r2BindingName = this.findR2BindingName(manifestEntry);
|
|
192
|
+
const bucket = this.env?.[r2BindingName];
|
|
193
|
+
if (!bucket) {
|
|
194
|
+
throw new Error(`Cloudflare R2 binding ${r2BindingName} is required to serve ${manifestEntry.path}.`);
|
|
195
|
+
}
|
|
196
|
+
const object = await bucket.get(manifestEntry.storageKey);
|
|
197
|
+
if (!object) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const arrayBuffer = typeof object.arrayBuffer === 'function'
|
|
201
|
+
? await object.arrayBuffer()
|
|
202
|
+
: object.body instanceof ReadableStream
|
|
203
|
+
? await new Response(object.body).arrayBuffer()
|
|
204
|
+
: object.body && 'arrayBuffer' in object.body
|
|
205
|
+
? await object.body.arrayBuffer()
|
|
206
|
+
: new ArrayBuffer(0);
|
|
207
|
+
return {
|
|
208
|
+
path: manifestEntry.path,
|
|
209
|
+
kind: 'binary',
|
|
210
|
+
mediaType: manifestEntry.mediaType,
|
|
211
|
+
bytes: new Uint8Array(arrayBuffer),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async listDirectory(contentPath) {
|
|
215
|
+
return this.storeIndex.listDirectory(contentPath);
|
|
216
|
+
}
|
|
217
|
+
findR2BindingName(entry) {
|
|
218
|
+
if (entry.storageKind !== 'r2') {
|
|
219
|
+
return 'ASSETS';
|
|
220
|
+
}
|
|
221
|
+
return this.runtime?.r2Binding ?? 'MDORIGIN_R2';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
68
224
|
function decodeBase64(value) {
|
|
69
225
|
const decoded = atob(value);
|
|
70
226
|
return Uint8Array.from(decoded, (character) => character.charCodeAt(0));
|
package/dist/adapters/node.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { getMediaTypeForPath, isLikelyTextPath, normalizeContentPath, normalizeDirectoryPath, } from '../core/content-store.js';
|
|
4
|
+
import { getMediaTypeForPath, isIgnoredContentName, isLikelyTextPath, normalizeContentPath, normalizeDirectoryPath, } from '../core/content-store.js';
|
|
5
5
|
import { handleSiteRequest } from '../core/request-handler.js';
|
|
6
6
|
export function createFileSystemContentStore(rootDir) {
|
|
7
7
|
const resolvedRootDir = path.resolve(rootDir);
|
|
@@ -61,7 +61,7 @@ export function createFileSystemContentStore(rootDir) {
|
|
|
61
61
|
}
|
|
62
62
|
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
63
63
|
const resolvedEntries = await Promise.all(entries
|
|
64
|
-
.filter((entry) => !entry.name
|
|
64
|
+
.filter((entry) => !isIgnoredContentName(entry.name))
|
|
65
65
|
.map(async (entry) => {
|
|
66
66
|
const childVisiblePath = normalizedPath === ''
|
|
67
67
|
? entry.name
|
|
@@ -5,11 +5,11 @@ import { applySiteConfigFrontmatterDefaults, loadUserSiteConfig, } from '../core
|
|
|
5
5
|
export async function runBuildCloudflareCommand(argv) {
|
|
6
6
|
const args = parseArgs(argv);
|
|
7
7
|
if (args.help) {
|
|
8
|
-
console.log('Usage: mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search]');
|
|
8
|
+
console.log('Usage: mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search] [--binary-mode inline|external] [--assets-max-bytes 26214400] [--r2-binding MDORIGIN_R2]');
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
if (!args.root) {
|
|
12
|
-
console.error('Usage: mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search]');
|
|
12
|
+
console.error('Usage: mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search] [--binary-mode inline|external] [--assets-max-bytes 26214400] [--r2-binding MDORIGIN_R2]');
|
|
13
13
|
process.exitCode = 1;
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
@@ -27,6 +27,9 @@ export async function runBuildCloudflareCommand(argv) {
|
|
|
27
27
|
siteConfig,
|
|
28
28
|
searchDir: args.search ? path.resolve(args.search) : undefined,
|
|
29
29
|
configModulePath: loadedConfig.configModulePath,
|
|
30
|
+
binaryMode: args.binaryMode,
|
|
31
|
+
assetsMaxBytes: args.assetsMaxBytes,
|
|
32
|
+
r2Binding: args.r2Binding,
|
|
30
33
|
});
|
|
31
34
|
console.log(`cloudflare worker written to ${result.workerFile}`);
|
|
32
35
|
}
|
|
@@ -59,6 +62,28 @@ function parseArgs(argv) {
|
|
|
59
62
|
index += 1;
|
|
60
63
|
continue;
|
|
61
64
|
}
|
|
65
|
+
if (argument === '--binary-mode' && nextValue) {
|
|
66
|
+
if (nextValue !== 'inline' && nextValue !== 'external') {
|
|
67
|
+
throw new Error(`Invalid binary mode for mdorigin build cloudflare: ${nextValue}`);
|
|
68
|
+
}
|
|
69
|
+
result.binaryMode = nextValue;
|
|
70
|
+
index += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (argument === '--assets-max-bytes' && nextValue) {
|
|
74
|
+
const parsed = Number.parseInt(nextValue, 10);
|
|
75
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
76
|
+
throw new Error(`Invalid value for --assets-max-bytes: ${nextValue}`);
|
|
77
|
+
}
|
|
78
|
+
result.assetsMaxBytes = parsed;
|
|
79
|
+
index += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (argument === '--r2-binding' && nextValue) {
|
|
83
|
+
result.r2Binding = nextValue;
|
|
84
|
+
index += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
62
87
|
throw new Error(`Unknown argument for mdorigin build cloudflare: ${argument}`);
|
|
63
88
|
}
|
|
64
89
|
return result;
|
package/dist/cli/help.js
CHANGED
|
@@ -3,8 +3,9 @@ export const ROOT_USAGE_LINES = [
|
|
|
3
3
|
' mdorigin dev --root <content-dir> [--port 3000] [--config <config-file>] [--search ./dist/search]',
|
|
4
4
|
' mdorigin build index (--root <content-dir> | --dir <content-dir>) [--config <config-file>]',
|
|
5
5
|
' mdorigin build search --root <content-dir> [--out ./dist/search] [--embedding-backend model2vec|hashing] [--model sentence-transformers/all-MiniLM-L6-v2] [--config <config-file>]',
|
|
6
|
-
' mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search]',
|
|
7
|
-
' mdorigin init cloudflare [--dir .] [--entry ./dist/cloudflare/worker.mjs] [--name <worker-name>] [--compatibility-date 2026-03-20] [--force]',
|
|
6
|
+
' mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search] [--binary-mode inline|external] [--assets-max-bytes 26214400] [--r2-binding MDORIGIN_R2]',
|
|
7
|
+
' mdorigin init cloudflare [--dir .] [--entry ./dist/cloudflare/worker.mjs] [--name <worker-name>] [--compatibility-date 2026-03-20] [--r2-bucket <bucket-name>] [--force]',
|
|
8
|
+
' mdorigin sync cloudflare-r2 --dir ./dist/cloudflare --bucket <bucket-name> [--force]',
|
|
8
9
|
' mdorigin search --index <search-dir> [--top-k 10] <query>',
|
|
9
10
|
' mdorigin version',
|
|
10
11
|
'',
|
|
@@ -16,11 +17,11 @@ export const BUILD_USAGE_LINES = [
|
|
|
16
17
|
'Usage:',
|
|
17
18
|
' mdorigin build index (--root <content-dir> | --dir <content-dir>) [--config <config-file>]',
|
|
18
19
|
' mdorigin build search --root <content-dir> [--out ./dist/search] [--embedding-backend model2vec|hashing] [--model sentence-transformers/all-MiniLM-L6-v2] [--config <config-file>]',
|
|
19
|
-
' mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search]',
|
|
20
|
+
' mdorigin build cloudflare --root <content-dir> [--out ./dist/cloudflare] [--config <config-file>] [--search ./dist/search] [--binary-mode inline|external] [--assets-max-bytes 26214400] [--r2-binding MDORIGIN_R2]',
|
|
20
21
|
];
|
|
21
22
|
export const INIT_USAGE_LINES = [
|
|
22
23
|
'Usage:',
|
|
23
|
-
' mdorigin init cloudflare [--dir .] [--entry ./dist/cloudflare/worker.mjs] [--name <worker-name>] [--compatibility-date 2026-03-20] [--force]',
|
|
24
|
+
' mdorigin init cloudflare [--dir .] [--entry ./dist/cloudflare/worker.mjs] [--name <worker-name>] [--compatibility-date 2026-03-20] [--r2-bucket <bucket-name>] [--force]',
|
|
24
25
|
];
|
|
25
26
|
export function printUsage(lines, error = false) {
|
|
26
27
|
const output = lines.join('\n');
|
|
@@ -4,7 +4,7 @@ import { initCloudflareProject } from '../cloudflare.js';
|
|
|
4
4
|
export async function runInitCloudflareCommand(argv) {
|
|
5
5
|
const args = parseArgs(argv);
|
|
6
6
|
if (args.help) {
|
|
7
|
-
console.log('Usage: mdorigin init cloudflare [--dir .] [--entry ./dist/cloudflare/worker.mjs] [--name <worker-name>] [--compatibility-date 2026-03-20] [--force]');
|
|
7
|
+
console.log('Usage: mdorigin init cloudflare [--dir .] [--entry ./dist/cloudflare/worker.mjs] [--name <worker-name>] [--compatibility-date 2026-03-20] [--r2-bucket <bucket-name>] [--force]');
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
10
10
|
const projectDir = path.resolve(args.dir ?? '.');
|
|
@@ -16,6 +16,7 @@ export async function runInitCloudflareCommand(argv) {
|
|
|
16
16
|
workerName: args.name,
|
|
17
17
|
siteTitle,
|
|
18
18
|
compatibilityDate: args.compatibilityDate,
|
|
19
|
+
r2Bucket: args.r2Bucket,
|
|
19
20
|
force: args.force,
|
|
20
21
|
});
|
|
21
22
|
console.log(`wrangler config written to ${result.configFile}`);
|
|
@@ -49,6 +50,11 @@ function parseArgs(argv) {
|
|
|
49
50
|
index += 1;
|
|
50
51
|
continue;
|
|
51
52
|
}
|
|
53
|
+
if (argument === '--r2-bucket' && nextValue) {
|
|
54
|
+
result.r2Bucket = nextValue;
|
|
55
|
+
index += 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
52
58
|
if (argument === '--force') {
|
|
53
59
|
result.force = true;
|
|
54
60
|
continue;
|
package/dist/cli/main.js
CHANGED
|
@@ -9,6 +9,7 @@ import { runDevCommand } from './dev.js';
|
|
|
9
9
|
import { BUILD_USAGE_LINES, INIT_USAGE_LINES, ROOT_USAGE_LINES, printUsage } from './help.js';
|
|
10
10
|
import { runInitCloudflareCommand } from './init-cloudflare.js';
|
|
11
11
|
import { runSearchCommand } from './search.js';
|
|
12
|
+
import { runSyncCloudflareR2Command } from './sync-cloudflare-r2.js';
|
|
12
13
|
async function main() {
|
|
13
14
|
const argv = process.argv.slice(2);
|
|
14
15
|
const [command, subcommand, ...rest] = argv;
|
|
@@ -62,6 +63,10 @@ async function main() {
|
|
|
62
63
|
await runSearchCommand([subcommand, ...rest].filter(isDefined));
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
66
|
+
if (command === 'sync' && subcommand === 'cloudflare-r2') {
|
|
67
|
+
await runSyncCloudflareR2Command(rest);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
65
70
|
console.error(`Unknown command: ${argv.join(' ')}`);
|
|
66
71
|
printUsage(ROOT_USAGE_LINES, true);
|
|
67
72
|
process.exitCode = 1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSyncCloudflareR2Command(argv: string[]): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { syncCloudflareR2 } from '../cloudflare.js';
|
|
3
|
+
export async function runSyncCloudflareR2Command(argv) {
|
|
4
|
+
const args = parseArgs(argv);
|
|
5
|
+
if (args.help) {
|
|
6
|
+
console.log('Usage: mdorigin sync cloudflare-r2 --dir ./dist/cloudflare --bucket <bucket-name> [--force]');
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!args.bucket) {
|
|
10
|
+
console.error('Usage: mdorigin sync cloudflare-r2 --dir ./dist/cloudflare --bucket <bucket-name> [--force]');
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const result = await syncCloudflareR2({
|
|
15
|
+
dir: path.resolve(args.dir ?? 'dist/cloudflare'),
|
|
16
|
+
bucketName: args.bucket,
|
|
17
|
+
force: args.force,
|
|
18
|
+
});
|
|
19
|
+
console.log(`synced ${result.uploadedCount} R2 object(s), skipped ${result.skippedCount}, state written to ${result.stateFile}`);
|
|
20
|
+
}
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
const result = {};
|
|
23
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
24
|
+
const argument = argv[index];
|
|
25
|
+
const nextValue = argv[index + 1];
|
|
26
|
+
if (argument === '--help' || argument === '-h') {
|
|
27
|
+
result.help = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (argument === '--dir' && nextValue) {
|
|
31
|
+
result.dir = nextValue;
|
|
32
|
+
index += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (argument === '--bucket' && nextValue) {
|
|
36
|
+
result.bucket = nextValue;
|
|
37
|
+
index += 1;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (argument === '--force') {
|
|
41
|
+
result.force = true;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Unknown argument for mdorigin sync cloudflare-r2: ${argument}`);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { createCloudflareWorker } from './adapters/cloudflare.js';
|
|
2
|
-
export type { CloudflareManifest, CloudflareManifestEntry, ExportedHandlerLike, } from './adapters/cloudflare.js';
|
|
2
|
+
export type { CloudflareBundleRuntimeConfig, CloudflareManifest, CloudflareManifestEntry, ExportedHandlerLike, } from './adapters/cloudflare.js';
|
package/dist/cloudflare.d.ts
CHANGED
|
@@ -1,34 +1,70 @@
|
|
|
1
|
-
import type { CloudflareManifest, CloudflareManifestEntry } from './adapters/cloudflare.js';
|
|
1
|
+
import type { CloudflareBundleRuntimeConfig, CloudflareManifest, CloudflareManifestEntry } from './adapters/cloudflare.js';
|
|
2
2
|
import { createCloudflareWorker } from './adapters/cloudflare.js';
|
|
3
3
|
import type { ResolvedSiteConfig } from './core/site-config.js';
|
|
4
4
|
export { createCloudflareWorker };
|
|
5
|
-
export type { CloudflareManifest, CloudflareManifestEntry };
|
|
5
|
+
export type { CloudflareBundleRuntimeConfig, CloudflareManifest, CloudflareManifestEntry, };
|
|
6
|
+
export type CloudflareBinaryMode = 'inline' | 'external';
|
|
6
7
|
export interface BuildCloudflareManifestOptions {
|
|
7
8
|
rootDir: string;
|
|
8
9
|
siteConfig: ResolvedSiteConfig;
|
|
9
10
|
searchDir?: string;
|
|
11
|
+
binaryMode?: CloudflareBinaryMode;
|
|
12
|
+
assetsMaxBytes?: number;
|
|
13
|
+
r2Binding?: string;
|
|
10
14
|
}
|
|
11
|
-
export interface WriteCloudflareBundleOptions {
|
|
12
|
-
rootDir: string;
|
|
15
|
+
export interface WriteCloudflareBundleOptions extends BuildCloudflareManifestOptions {
|
|
13
16
|
outDir: string;
|
|
14
|
-
siteConfig: ResolvedSiteConfig;
|
|
15
17
|
packageImport?: string;
|
|
16
|
-
searchDir?: string;
|
|
17
18
|
configModulePath?: string;
|
|
18
19
|
}
|
|
20
|
+
export interface CloudflareBundleMetadata {
|
|
21
|
+
version: 1;
|
|
22
|
+
workerEntry: string;
|
|
23
|
+
binaryMode: CloudflareBinaryMode;
|
|
24
|
+
assetsMaxBytes?: number;
|
|
25
|
+
assetsDir?: string;
|
|
26
|
+
r2Dir?: string;
|
|
27
|
+
r2Binding?: string;
|
|
28
|
+
siteTitle?: string;
|
|
29
|
+
r2Objects: Array<{
|
|
30
|
+
path: string;
|
|
31
|
+
mediaType: string;
|
|
32
|
+
storageKey: string;
|
|
33
|
+
file: string;
|
|
34
|
+
byteSize: number;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
19
37
|
export interface InitCloudflareProjectOptions {
|
|
20
38
|
projectDir: string;
|
|
21
39
|
workerEntry: string;
|
|
22
40
|
workerName?: string;
|
|
23
41
|
siteTitle?: string;
|
|
24
42
|
compatibilityDate?: string;
|
|
43
|
+
r2Bucket?: string;
|
|
44
|
+
force?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface SyncCloudflareR2Options {
|
|
47
|
+
dir: string;
|
|
48
|
+
bucketName: string;
|
|
25
49
|
force?: boolean;
|
|
50
|
+
runCommand?: typeof runWranglerCommand;
|
|
51
|
+
}
|
|
52
|
+
export interface SyncCloudflareR2Result {
|
|
53
|
+
uploadedCount: number;
|
|
54
|
+
skippedCount: number;
|
|
55
|
+
stateFile: string;
|
|
26
56
|
}
|
|
27
57
|
export declare function buildCloudflareManifest(options: BuildCloudflareManifestOptions): Promise<CloudflareManifest>;
|
|
28
58
|
export declare function writeCloudflareBundle(options: WriteCloudflareBundleOptions): Promise<{
|
|
29
59
|
manifest: CloudflareManifest;
|
|
30
60
|
workerFile: string;
|
|
61
|
+
bundleFile: string;
|
|
31
62
|
}>;
|
|
32
63
|
export declare function initCloudflareProject(options: InitCloudflareProjectOptions): Promise<{
|
|
33
64
|
configFile: string;
|
|
34
65
|
}>;
|
|
66
|
+
export declare function syncCloudflareR2(options: SyncCloudflareR2Options): Promise<SyncCloudflareR2Result>;
|
|
67
|
+
declare function runWranglerCommand(command: string, args: string[]): {
|
|
68
|
+
status: number | null;
|
|
69
|
+
stderr: string;
|
|
70
|
+
};
|