emdash-emit 0.1.14 → 0.1.16
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/CHANGELOG.md +96 -1
- package/README.md +259 -188
- package/dist/bin/emit.d.ts.map +1 -1
- package/dist/bin/emit.js +2 -0
- package/dist/bin/emit.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +6 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/media.d.ts +4 -0
- package/dist/cli/commands/media.d.ts.map +1 -0
- package/dist/cli/commands/media.js +128 -0
- package/dist/cli/commands/media.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +32 -0
- package/dist/cli/commands/pull.d.ts.map +1 -1
- package/dist/cli/commands/pull.js +136 -9
- package/dist/cli/commands/pull.js.map +1 -1
- package/dist/cli/commands/push.d.ts.map +1 -1
- package/dist/cli/commands/push.js +18 -0
- package/dist/cli/commands/push.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +16 -5
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/client/media.d.ts.map +1 -1
- package/dist/client/media.js +16 -5
- package/dist/client/media.js.map +1 -1
- package/dist/db/cache.d.ts +19 -4
- package/dist/db/cache.d.ts.map +1 -1
- package/dist/db/cache.js +127 -16
- package/dist/db/cache.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +7 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/sync-doctor.d.ts.map +1 -1
- package/dist/mcp/tools/sync-doctor.js +12 -0
- package/dist/mcp/tools/sync-doctor.js.map +1 -1
- package/dist/mcp/tools/sync-media-search.d.ts +4 -0
- package/dist/mcp/tools/sync-media-search.d.ts.map +1 -0
- package/dist/mcp/tools/sync-media-search.js +102 -0
- package/dist/mcp/tools/sync-media-search.js.map +1 -0
- package/dist/mcp/tools/sync-pull.d.ts.map +1 -1
- package/dist/mcp/tools/sync-pull.js +85 -13
- package/dist/mcp/tools/sync-pull.js.map +1 -1
- package/dist/mcp/tools/sync-push.d.ts.map +1 -1
- package/dist/mcp/tools/sync-push.js +5 -0
- package/dist/mcp/tools/sync-push.js.map +1 -1
- package/dist/mcp/tools/sync-status.d.ts.map +1 -1
- package/dist/mcp/tools/sync-status.js +6 -2
- package/dist/mcp/tools/sync-status.js.map +1 -1
- package/dist/sync/delete.d.ts +2 -1
- package/dist/sync/delete.d.ts.map +1 -1
- package/dist/sync/delete.js +6 -19
- package/dist/sync/delete.js.map +1 -1
- package/dist/sync/diff.d.ts.map +1 -1
- package/dist/sync/diff.js +18 -2
- package/dist/sync/diff.js.map +1 -1
- package/dist/sync/doctor.d.ts +27 -1
- package/dist/sync/doctor.d.ts.map +1 -1
- package/dist/sync/doctor.js +309 -26
- package/dist/sync/doctor.js.map +1 -1
- package/dist/sync/guards.d.ts +45 -0
- package/dist/sync/guards.d.ts.map +1 -0
- package/dist/sync/guards.js +236 -0
- package/dist/sync/guards.js.map +1 -0
- package/dist/sync/media/body_scan.d.ts +15 -0
- package/dist/sync/media/body_scan.d.ts.map +1 -0
- package/dist/sync/media/body_scan.js +132 -0
- package/dist/sync/media/body_scan.js.map +1 -0
- package/dist/sync/media/companions.d.ts +32 -0
- package/dist/sync/media/companions.d.ts.map +1 -0
- package/dist/sync/media/companions.js +260 -0
- package/dist/sync/media/companions.js.map +1 -0
- package/dist/sync/media/index.d.ts +9 -0
- package/dist/sync/media/index.d.ts.map +1 -0
- package/dist/sync/media/index.js +9 -0
- package/dist/sync/media/index.js.map +1 -0
- package/dist/sync/media/mirror_item.d.ts +27 -0
- package/dist/sync/media/mirror_item.d.ts.map +1 -0
- package/dist/sync/media/mirror_item.js +207 -0
- package/dist/sync/media/mirror_item.js.map +1 -0
- package/dist/sync/media/naming_mirror.d.ts +36 -0
- package/dist/sync/media/naming_mirror.d.ts.map +1 -0
- package/dist/sync/media/naming_mirror.js +143 -0
- package/dist/sync/media/naming_mirror.js.map +1 -0
- package/dist/sync/media/pull_article.d.ts +37 -0
- package/dist/sync/media/pull_article.d.ts.map +1 -0
- package/dist/sync/media/pull_article.js +273 -0
- package/dist/sync/media/pull_article.js.map +1 -0
- package/dist/sync/media/pull_library.d.ts +12 -0
- package/dist/sync/media/pull_library.d.ts.map +1 -0
- package/dist/sync/media/pull_library.js +255 -0
- package/dist/sync/media/pull_library.js.map +1 -0
- package/dist/sync/media/remote_index.d.ts +49 -0
- package/dist/sync/media/remote_index.d.ts.map +1 -0
- package/dist/sync/media/remote_index.js +241 -0
- package/dist/sync/media/remote_index.js.map +1 -0
- package/dist/sync/media/types.d.ts +237 -0
- package/dist/sync/media/types.d.ts.map +1 -0
- package/dist/sync/media/types.js +11 -0
- package/dist/sync/media/types.js.map +1 -0
- package/dist/sync/media.d.ts +1 -74
- package/dist/sync/media.d.ts.map +1 -1
- package/dist/sync/media.js +1 -592
- package/dist/sync/media.js.map +1 -1
- package/dist/sync/orchestrator.d.ts +23 -0
- package/dist/sync/orchestrator.d.ts.map +1 -1
- package/dist/sync/orchestrator.js +85 -1
- package/dist/sync/orchestrator.js.map +1 -1
- package/dist/sync/preflight.d.ts +1 -1
- package/dist/sync/preflight.d.ts.map +1 -1
- package/dist/sync/preflight.js +129 -17
- package/dist/sync/preflight.js.map +1 -1
- package/dist/sync/pull.d.ts +19 -0
- package/dist/sync/pull.d.ts.map +1 -1
- package/dist/sync/pull.js +169 -15
- package/dist/sync/pull.js.map +1 -1
- package/dist/sync/push.d.ts +7 -0
- package/dist/sync/push.d.ts.map +1 -1
- package/dist/sync/push.js +37 -10
- package/dist/sync/push.js.map +1 -1
- package/dist/sync/status.d.ts +4 -0
- package/dist/sync/status.d.ts.map +1 -1
- package/dist/sync/status.js +68 -4
- package/dist/sync/status.js.map +1 -1
- package/dist/utils/config.d.ts +25 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +28 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +15 -4
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/media-body-refs.d.ts +81 -0
- package/dist/utils/media-body-refs.d.ts.map +1 -0
- package/dist/utils/media-body-refs.js +240 -0
- package/dist/utils/media-body-refs.js.map +1 -0
- package/dist/utils/media-refs.d.ts +44 -0
- package/dist/utils/media-refs.d.ts.map +1 -0
- package/dist/utils/media-refs.js +101 -0
- package/dist/utils/media-refs.js.map +1 -0
- package/dist/utils/naming.d.ts +31 -0
- package/dist/utils/naming.d.ts.map +1 -1
- package/dist/utils/naming.js +58 -0
- package/dist/utils/naming.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { unwrapEnvelope } from '../../client/api.js';
|
|
4
|
+
import { ApiError } from '../../client/error.js';
|
|
5
|
+
import { createAuthedFetch } from '../../client/http.js';
|
|
6
|
+
import { DEFAULT_CONFIG } from '../../utils/config.js';
|
|
7
|
+
import { collectArticleStorageKeys } from './body_scan.js';
|
|
8
|
+
import { collectLocalMediaMirrorCandidates } from './companions.js';
|
|
9
|
+
import { syncSingleMediaItem } from './mirror_item.js';
|
|
10
|
+
import { mediaMirrorNames, mediaRecordIdForStem } from './naming_mirror.js';
|
|
11
|
+
import { pullArticleMedia } from './pull_article.js';
|
|
12
|
+
import { collectRemoteStorageKeys, findUnmirroredStorageKeys } from './remote_index.js';
|
|
13
|
+
import { MEDIA_LIST_PAGE_SIZE, } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Pulls media metadata and files from EmDash API into media/ directory with cursor pagination.
|
|
16
|
+
* media/<markdownRef>.yaml
|
|
17
|
+
* media/<markdownRef>.<ext>
|
|
18
|
+
*
|
|
19
|
+
* `markdownRef` is the sanitized storageKey basename (`storageKeyToMarkdownRef`), never the record id, so the
|
|
20
|
+
* mirror on disk shares one stem with the body URL that references it; the original `filename` survives only
|
|
21
|
+
* inside the companion YAML. See `mediaMirrorNames()` for the full naming and collision rule.
|
|
22
|
+
*/
|
|
23
|
+
export async function pullMedia(options) {
|
|
24
|
+
if (options.storageKeys && options.storageKeys.length > 0) {
|
|
25
|
+
const fetchFn = createAuthedFetch(options);
|
|
26
|
+
const mediaDir = path.join(options.workspaceRoot, 'media');
|
|
27
|
+
fs.mkdirSync(mediaDir, { recursive: true });
|
|
28
|
+
const downloadFiles = options.downloadFiles !== false;
|
|
29
|
+
// Walk metadata via collectRemoteStorageKeys (walks library to index records by storageKey)
|
|
30
|
+
const remoteIndex = await collectRemoteStorageKeys({
|
|
31
|
+
workspaceRoot: options.workspaceRoot,
|
|
32
|
+
origin: options.origin,
|
|
33
|
+
token: options.token,
|
|
34
|
+
refreshToken: options.refreshToken,
|
|
35
|
+
fetchFn,
|
|
36
|
+
});
|
|
37
|
+
let totalProcessed = 0;
|
|
38
|
+
let downloadedCount = 0;
|
|
39
|
+
let skippedCount = 0;
|
|
40
|
+
const orphanStorageKeys = [];
|
|
41
|
+
for (const key of options.storageKeys) {
|
|
42
|
+
const item = remoteIndex.recordsByStorageKey.get(key);
|
|
43
|
+
if (!item) {
|
|
44
|
+
orphanStorageKeys.push(key);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
totalProcessed++;
|
|
48
|
+
const result = await syncSingleMediaItem({
|
|
49
|
+
item,
|
|
50
|
+
mediaDir,
|
|
51
|
+
origin: options.origin,
|
|
52
|
+
token: options.token,
|
|
53
|
+
downloadFiles,
|
|
54
|
+
fetchFn,
|
|
55
|
+
cache: options.cache,
|
|
56
|
+
});
|
|
57
|
+
if (result.downloaded)
|
|
58
|
+
downloadedCount++;
|
|
59
|
+
else if (result.skipped)
|
|
60
|
+
skippedCount++;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
total: totalProcessed,
|
|
64
|
+
downloaded: downloadedCount,
|
|
65
|
+
skipped: skippedCount,
|
|
66
|
+
orphanStorageKeys: orphanStorageKeys.length > 0 ? orphanStorageKeys : undefined,
|
|
67
|
+
remoteTotalCount: remoteIndex.complete
|
|
68
|
+
? (remoteIndex.totalCount ?? remoteIndex.scannedCount)
|
|
69
|
+
: remoteIndex.totalCount,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (options.article) {
|
|
73
|
+
return pullArticleMedia({
|
|
74
|
+
workspaceRoot: options.workspaceRoot,
|
|
75
|
+
origin: options.origin,
|
|
76
|
+
token: options.token,
|
|
77
|
+
refreshToken: options.refreshToken,
|
|
78
|
+
article: options.article,
|
|
79
|
+
collection: options.collection,
|
|
80
|
+
downloadFiles: options.downloadFiles,
|
|
81
|
+
fetchFn: options.fetchFn,
|
|
82
|
+
cache: options.cache,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const fetchFn = createAuthedFetch(options);
|
|
86
|
+
const targetTotal = options.all ? Infinity : (options.limit ?? DEFAULT_CONFIG.mediaSyncLimit);
|
|
87
|
+
const downloadFiles = options.downloadFiles !== false;
|
|
88
|
+
const batchSize = MEDIA_LIST_PAGE_SIZE;
|
|
89
|
+
const mediaDir = path.join(options.workspaceRoot, 'media');
|
|
90
|
+
fs.mkdirSync(mediaDir, { recursive: true });
|
|
91
|
+
// Every mirrored companion is a confirmed storageKey source, including the ones written before Emit
|
|
92
|
+
// persisted the field, whose `url:` still names the key. Seeding from disk means `--prune` never
|
|
93
|
+
// deletes an asset that this run's media window simply did not reach.
|
|
94
|
+
const activeStorageKeys = new Set();
|
|
95
|
+
for (const candidate of collectLocalMediaMirrorCandidates(mediaDir).values()) {
|
|
96
|
+
if (candidate.storageKey)
|
|
97
|
+
activeStorageKeys.add(candidate.storageKey);
|
|
98
|
+
}
|
|
99
|
+
// Metadata for the coverage report and the storageKey census. `pullMedia` already walks the library
|
|
100
|
+
// cursor-by-cursor, so it owns the index instead of paying for a second one.
|
|
101
|
+
const remoteIndex = {
|
|
102
|
+
storageKeys: activeStorageKeys,
|
|
103
|
+
mediaIdsByStorageKey: new Map(),
|
|
104
|
+
recordsByStorageKey: new Map(),
|
|
105
|
+
recordsById: new Map(),
|
|
106
|
+
totalCount: null,
|
|
107
|
+
complete: false,
|
|
108
|
+
scannedCount: 0,
|
|
109
|
+
};
|
|
110
|
+
const activeMediaBaseNames = new Set();
|
|
111
|
+
// Only records the remote walk actually reports may authorise a mirror to stay. Seeding this from local
|
|
112
|
+
// companions instead would make every existing file self-exempt and prune nothing at all.
|
|
113
|
+
const activeMediaIds = new Set();
|
|
114
|
+
let totalProcessed = 0;
|
|
115
|
+
let downloadedCount = 0;
|
|
116
|
+
let skippedCount = 0;
|
|
117
|
+
let cursor = undefined;
|
|
118
|
+
while (totalProcessed < targetTotal) {
|
|
119
|
+
const remaining = targetTotal - totalProcessed;
|
|
120
|
+
const currentBatch = Math.min(remaining, batchSize);
|
|
121
|
+
const url = new URL(`/_emdash/api/media`, options.origin);
|
|
122
|
+
url.searchParams.set('limit', String(currentBatch));
|
|
123
|
+
if (cursor) {
|
|
124
|
+
url.searchParams.set('cursor', cursor);
|
|
125
|
+
}
|
|
126
|
+
const res = await fetchFn(url.toString(), {
|
|
127
|
+
method: 'GET',
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: `Bearer ${options.token}`,
|
|
130
|
+
Accept: 'application/json',
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
if (!res.ok) {
|
|
134
|
+
throw new ApiError({
|
|
135
|
+
message: `Failed to list media: ${res.statusText}`,
|
|
136
|
+
code: 'MEDIA_LIST_FAILED',
|
|
137
|
+
status: res.status,
|
|
138
|
+
endpoint: url.toString(),
|
|
139
|
+
suggestion: 'Verify network connection and token permissions.',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const json = await res.json();
|
|
143
|
+
const data = unwrapEnvelope(json);
|
|
144
|
+
if (typeof data.totalCount === 'number')
|
|
145
|
+
remoteIndex.totalCount = data.totalCount;
|
|
146
|
+
const rawItems = (Array.isArray(data.items) ? data.items : Array.isArray(data) ? data : []);
|
|
147
|
+
if (rawItems.length === 0) {
|
|
148
|
+
// An empty page ends the listing regardless of any cursor the server echoes back.
|
|
149
|
+
remoteIndex.complete = true;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
for (const item of rawItems) {
|
|
153
|
+
const names = mediaMirrorNames(item);
|
|
154
|
+
activeMediaBaseNames.add(names.markdownRef);
|
|
155
|
+
for (const legacy of names.legacyBaseNames) {
|
|
156
|
+
activeMediaBaseNames.add(legacy);
|
|
157
|
+
}
|
|
158
|
+
activeMediaIds.add(item.id);
|
|
159
|
+
if (item.storageKey) {
|
|
160
|
+
activeStorageKeys.add(item.storageKey);
|
|
161
|
+
remoteIndex.mediaIdsByStorageKey.set(item.storageKey, item.id);
|
|
162
|
+
}
|
|
163
|
+
const result = await syncSingleMediaItem({
|
|
164
|
+
item,
|
|
165
|
+
mediaDir,
|
|
166
|
+
origin: options.origin,
|
|
167
|
+
token: options.token,
|
|
168
|
+
downloadFiles,
|
|
169
|
+
fetchFn,
|
|
170
|
+
cache: options.cache,
|
|
171
|
+
});
|
|
172
|
+
if (result.downloaded)
|
|
173
|
+
downloadedCount++;
|
|
174
|
+
else if (result.skipped)
|
|
175
|
+
skippedCount++;
|
|
176
|
+
totalProcessed++;
|
|
177
|
+
remoteIndex.scannedCount++;
|
|
178
|
+
if (totalProcessed >= targetTotal) {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!data.nextCursor || typeof data.nextCursor !== 'string') {
|
|
183
|
+
remoteIndex.complete = true;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
cursor = data.nextCursor;
|
|
187
|
+
}
|
|
188
|
+
// Pruning logic for media: if --prune or --all is enabled, delete local media not present on remote
|
|
189
|
+
let deletedCount = 0;
|
|
190
|
+
if ((options.prune || options.all) && fs.existsSync(mediaDir)) {
|
|
191
|
+
const files = fs.readdirSync(mediaDir);
|
|
192
|
+
for (const file of files) {
|
|
193
|
+
// Leftover `legacy_*` mirrors from the removed file-route fallback are never matched by the
|
|
194
|
+
// mediaId pattern below, so they survive pruning and must be deleted by hand.
|
|
195
|
+
if (file.startsWith('legacy_'))
|
|
196
|
+
continue;
|
|
197
|
+
// A mirror is identified with its companion YAML: the stem names the storageKey, and the record id
|
|
198
|
+
// lives inside the companion. Only a file Emit can attribute to a record may be pruned — an
|
|
199
|
+
// unattributable migrated name is kept, because deleting an asset over a guess is unrecoverable.
|
|
200
|
+
const stem = file.replace(/\.[^.]+$/, '');
|
|
201
|
+
if (activeMediaBaseNames.has(stem) || activeMediaBaseNames.has(stem.replace(/^\d{8}_/, ''))) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const companionId = mediaRecordIdForStem(mediaDir, stem);
|
|
205
|
+
if (companionId && !activeMediaIds.has(companionId)) {
|
|
206
|
+
try {
|
|
207
|
+
fs.unlinkSync(path.join(mediaDir, file));
|
|
208
|
+
deletedCount++;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Ignore unlink errors
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// A library-wide walk is exhaustive, so it doubles as the storageKey census: every article reference
|
|
217
|
+
// that neither a local companion nor any remote record names is reported. The list endpoint exposes no
|
|
218
|
+
// storageKey filter, which is why the census requires the full walk `--all` already performs.
|
|
219
|
+
let referencedUnmirroredStorageKeys = undefined;
|
|
220
|
+
if (options.all) {
|
|
221
|
+
const referenced = new Set();
|
|
222
|
+
for (const status of ['draft', 'published']) {
|
|
223
|
+
const statusDir = path.join(options.workspaceRoot, options.collection || 'posts', status);
|
|
224
|
+
if (!fs.existsSync(statusDir))
|
|
225
|
+
continue;
|
|
226
|
+
for (const file of fs.readdirSync(statusDir)) {
|
|
227
|
+
if (!file.endsWith('.md'))
|
|
228
|
+
continue;
|
|
229
|
+
try {
|
|
230
|
+
const content = fs.readFileSync(path.join(statusDir, file), 'utf8');
|
|
231
|
+
for (const descriptor of collectArticleStorageKeys(content)) {
|
|
232
|
+
referenced.add(descriptor.storageKey);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// An unreadable article contributes nothing to the census.
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (referenced.size > 0) {
|
|
241
|
+
referencedUnmirroredStorageKeys = findUnmirroredStorageKeys(referenced, activeStorageKeys, remoteIndex);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
total: totalProcessed,
|
|
246
|
+
downloaded: downloadedCount,
|
|
247
|
+
skipped: skippedCount,
|
|
248
|
+
deleted: deletedCount,
|
|
249
|
+
remoteTotalCount: remoteIndex.complete
|
|
250
|
+
? (remoteIndex.totalCount ?? remoteIndex.scannedCount)
|
|
251
|
+
: remoteIndex.totalCount,
|
|
252
|
+
referencedUnmirroredStorageKeys,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=pull_library.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull_library.js","sourceRoot":"","sources":["../../../src/sync/media/pull_library.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type LocalMirrorIndex, type MediaSearchResult, type RemoteStorageKeyIndex } from './types.js';
|
|
2
|
+
export declare const MEDIA_SEARCH_DEFAULT_LIMIT = 20;
|
|
3
|
+
export declare const MEDIA_SEARCH_MAX_LIMIT = 50;
|
|
4
|
+
/** Default scan ceiling. `maxScan: 0` means unlimited (a complete walk). */
|
|
5
|
+
export declare const MEDIA_SEARCH_DEFAULT_MAX_SCAN = 500;
|
|
6
|
+
export interface SearchRemoteMediaOptions {
|
|
7
|
+
workspaceRoot: string;
|
|
8
|
+
origin: string;
|
|
9
|
+
token: string;
|
|
10
|
+
refreshToken?: string;
|
|
11
|
+
/** Literal, case-insensitive substring match. Regex is never compiled from user input. */
|
|
12
|
+
query: string;
|
|
13
|
+
/** Max hits to return (default 20, clamped to 50). */
|
|
14
|
+
limit?: number;
|
|
15
|
+
/** Max records to read before stopping the walk (default 500; 0 = unlimited). */
|
|
16
|
+
maxScan?: number;
|
|
17
|
+
/** Restrict output to assets Emit already mirrors. The walk still covers the whole library. */
|
|
18
|
+
mirroredOnly?: boolean;
|
|
19
|
+
/** Decoration built by M2. When omitted, every hit reports mirrored: false. */
|
|
20
|
+
mirrorIndex?: LocalMirrorIndex;
|
|
21
|
+
fetchFn?: typeof fetch;
|
|
22
|
+
}
|
|
23
|
+
export declare function collectRemoteStorageKeys(options: {
|
|
24
|
+
workspaceRoot: string;
|
|
25
|
+
origin: string;
|
|
26
|
+
token: string;
|
|
27
|
+
refreshToken?: string;
|
|
28
|
+
/** Stop after this many records. Defaults to unlimited, which is the whole library. */
|
|
29
|
+
maxItems?: number;
|
|
30
|
+
fetchFn?: typeof fetch;
|
|
31
|
+
}): Promise<RemoteStorageKeyIndex>;
|
|
32
|
+
/**
|
|
33
|
+
* Searches the remote EmDash media library by keyword across filename, alt text, caption,
|
|
34
|
+
* storageKey, and mirrorStem.
|
|
35
|
+
*
|
|
36
|
+
* Adheres strictly to the 3 principles:
|
|
37
|
+
* 1. Does not send `q` to the server; walks and filters client-side.
|
|
38
|
+
* 2. Matches are literal substring comparisons (case-insensitive, no regex).
|
|
39
|
+
* 3. Epistemic completeness: Reports scannedCount and walkComplete.
|
|
40
|
+
*/
|
|
41
|
+
export declare function searchRemoteMedia(options: SearchRemoteMediaOptions): Promise<MediaSearchResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Reports body storageKeys that no local mirror names and that a full remote library walk does not
|
|
44
|
+
* contain. A key is only listed when the walk was complete: an early stop proves nothing about the
|
|
45
|
+
* rest of the library, so an incomplete walk yields nothing. Even a confirmed absence is reported as a
|
|
46
|
+
* lookup result rather than as a missing record — Emit mirrors records and never repairs server data.
|
|
47
|
+
*/
|
|
48
|
+
export declare function findUnmirroredStorageKeys(referenced: Iterable<string>, localMirrorKeys: Iterable<string>, remoteIndex: RemoteStorageKeyIndex): string[];
|
|
49
|
+
//# sourceMappingURL=remote_index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote_index.d.ts","sourceRoot":"","sources":["../../../src/sync/media/remote_index.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,gBAAgB,EAErB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAC7C,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,4EAA4E;AAC5E,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAEjD,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAiDD,wBAAsB,wBAAwB,CAAC,OAAO,EAAE;IACtD,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA4DjC;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CAmI5B;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC5B,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,EACjC,WAAW,EAAE,qBAAqB,GACjC,MAAM,EAAE,CASV"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { unwrapEnvelope } from '../../client/api.js';
|
|
3
|
+
import { ApiError } from '../../client/error.js';
|
|
4
|
+
import { createAuthedFetch } from '../../client/http.js';
|
|
5
|
+
import { storageKeyToMarkdownRef } from '../../utils/naming.js';
|
|
6
|
+
import { MEDIA_LIST_PAGE_SIZE, } from './types.js';
|
|
7
|
+
export const MEDIA_SEARCH_DEFAULT_LIMIT = 20;
|
|
8
|
+
export const MEDIA_SEARCH_MAX_LIMIT = 50;
|
|
9
|
+
/** Default scan ceiling. `maxScan: 0` means unlimited (a complete walk). */
|
|
10
|
+
export const MEDIA_SEARCH_DEFAULT_MAX_SCAN = 500;
|
|
11
|
+
/**
|
|
12
|
+
* Fetches a single page from EmDash media list endpoint.
|
|
13
|
+
* Shared by collectRemoteStorageKeys and searchRemoteMedia to guarantee identical pagination behavior.
|
|
14
|
+
*/
|
|
15
|
+
async function fetchMediaListPage(options) {
|
|
16
|
+
const url = new URL('/_emdash/api/media', options.origin);
|
|
17
|
+
url.searchParams.set('limit', String(options.limit));
|
|
18
|
+
if (options.cursor)
|
|
19
|
+
url.searchParams.set('cursor', options.cursor);
|
|
20
|
+
const res = await options.fetchFn(url.toString(), {
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: `Bearer ${options.token}`,
|
|
23
|
+
Accept: 'application/json',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
throw new ApiError({
|
|
28
|
+
message: `Failed to list media: ${res.statusText}`,
|
|
29
|
+
code: 'MEDIA_LIST_FAILED',
|
|
30
|
+
status: res.status,
|
|
31
|
+
endpoint: url.toString(),
|
|
32
|
+
suggestion: 'Verify network connection and token permissions.',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const data = unwrapEnvelope(await res.json());
|
|
36
|
+
const totalCount = typeof data.totalCount === 'number' ? data.totalCount : null;
|
|
37
|
+
const rawItems = (Array.isArray(data.items) ? data.items : Array.isArray(data.data) ? data.data : []);
|
|
38
|
+
const nextCursor = typeof data.nextCursor === 'string' && data.nextCursor ? data.nextCursor : undefined;
|
|
39
|
+
return { rawItems, nextCursor, totalCount };
|
|
40
|
+
}
|
|
41
|
+
export async function collectRemoteStorageKeys(options) {
|
|
42
|
+
const fetchFn = createAuthedFetch(options);
|
|
43
|
+
const batchSize = MEDIA_LIST_PAGE_SIZE;
|
|
44
|
+
const maxItems = options.maxItems ?? Infinity;
|
|
45
|
+
const storageKeys = new Set();
|
|
46
|
+
const mediaIdsByStorageKey = new Map();
|
|
47
|
+
const recordsByStorageKey = new Map();
|
|
48
|
+
const recordsById = new Map();
|
|
49
|
+
let totalCount = null;
|
|
50
|
+
let scanned = 0;
|
|
51
|
+
let complete = false;
|
|
52
|
+
let cursor = undefined;
|
|
53
|
+
for (;;) {
|
|
54
|
+
const remaining = maxItems - scanned;
|
|
55
|
+
if (remaining <= 0)
|
|
56
|
+
break;
|
|
57
|
+
const currentBatch = Math.min(remaining, batchSize);
|
|
58
|
+
const page = await fetchMediaListPage({
|
|
59
|
+
origin: options.origin,
|
|
60
|
+
token: options.token,
|
|
61
|
+
limit: currentBatch,
|
|
62
|
+
cursor,
|
|
63
|
+
fetchFn,
|
|
64
|
+
});
|
|
65
|
+
if (page.totalCount !== null)
|
|
66
|
+
totalCount = page.totalCount;
|
|
67
|
+
for (const item of page.rawItems) {
|
|
68
|
+
scanned++;
|
|
69
|
+
if (item.id)
|
|
70
|
+
recordsById.set(item.id.toUpperCase(), item);
|
|
71
|
+
if (typeof item.storageKey === 'string' && item.storageKey) {
|
|
72
|
+
storageKeys.add(item.storageKey);
|
|
73
|
+
// First record wins a repeated key, so reporting a match stays deterministic.
|
|
74
|
+
if (item.id && !mediaIdsByStorageKey.has(item.storageKey)) {
|
|
75
|
+
mediaIdsByStorageKey.set(item.storageKey, item.id);
|
|
76
|
+
}
|
|
77
|
+
if (!recordsByStorageKey.has(item.storageKey)) {
|
|
78
|
+
recordsByStorageKey.set(item.storageKey, item);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (page.rawItems.length === 0 || !page.nextCursor) {
|
|
83
|
+
complete = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
cursor = page.nextCursor;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
storageKeys,
|
|
90
|
+
mediaIdsByStorageKey,
|
|
91
|
+
recordsByStorageKey,
|
|
92
|
+
recordsById,
|
|
93
|
+
totalCount,
|
|
94
|
+
complete,
|
|
95
|
+
scannedCount: scanned,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Searches the remote EmDash media library by keyword across filename, alt text, caption,
|
|
100
|
+
* storageKey, and mirrorStem.
|
|
101
|
+
*
|
|
102
|
+
* Adheres strictly to the 3 principles:
|
|
103
|
+
* 1. Does not send `q` to the server; walks and filters client-side.
|
|
104
|
+
* 2. Matches are literal substring comparisons (case-insensitive, no regex).
|
|
105
|
+
* 3. Epistemic completeness: Reports scannedCount and walkComplete.
|
|
106
|
+
*/
|
|
107
|
+
export async function searchRemoteMedia(options) {
|
|
108
|
+
const trimmed = options.query?.trim();
|
|
109
|
+
if (!trimmed) {
|
|
110
|
+
throw new ApiError({
|
|
111
|
+
message: 'A non-empty search query is required.',
|
|
112
|
+
code: 'MEDIA_SEARCH_QUERY_REQUIRED',
|
|
113
|
+
status: 400,
|
|
114
|
+
suggestion: 'Provide a search keyword (filename, alt text, caption, or storageKey).',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const normalizedQuery = trimmed.toLowerCase();
|
|
118
|
+
const rawLimit = options.limit ?? MEDIA_SEARCH_DEFAULT_LIMIT;
|
|
119
|
+
const limit = Math.max(1, Math.min(rawLimit, MEDIA_SEARCH_MAX_LIMIT));
|
|
120
|
+
const maxScan = options.maxScan === 0
|
|
121
|
+
? Infinity
|
|
122
|
+
: typeof options.maxScan === 'number' && options.maxScan > 0
|
|
123
|
+
? options.maxScan
|
|
124
|
+
: MEDIA_SEARCH_DEFAULT_MAX_SCAN;
|
|
125
|
+
const fetchFn = createAuthedFetch(options);
|
|
126
|
+
const batchSize = MEDIA_LIST_PAGE_SIZE;
|
|
127
|
+
const hits = [];
|
|
128
|
+
let scanned = 0;
|
|
129
|
+
let cursor = undefined;
|
|
130
|
+
let totalCount = null;
|
|
131
|
+
let walkComplete = false;
|
|
132
|
+
let matchCount = 0;
|
|
133
|
+
for (;;) {
|
|
134
|
+
const remaining = maxScan - scanned;
|
|
135
|
+
if (remaining <= 0) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
const currentBatch = Math.min(remaining, batchSize);
|
|
139
|
+
const page = await fetchMediaListPage({
|
|
140
|
+
origin: options.origin,
|
|
141
|
+
token: options.token,
|
|
142
|
+
limit: currentBatch,
|
|
143
|
+
cursor,
|
|
144
|
+
fetchFn,
|
|
145
|
+
});
|
|
146
|
+
if (page.totalCount !== null) {
|
|
147
|
+
totalCount = page.totalCount;
|
|
148
|
+
}
|
|
149
|
+
for (const item of page.rawItems) {
|
|
150
|
+
scanned++;
|
|
151
|
+
const storageKey = item.storageKey || null;
|
|
152
|
+
const mirrorEntry = storageKey && options.mirrorIndex ? options.mirrorIndex.get(storageKey) : undefined;
|
|
153
|
+
const mirrored = Boolean(mirrorEntry);
|
|
154
|
+
if (options.mirroredOnly && !mirrored) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const effectiveAlt = item.alt || mirrorEntry?.alt || null;
|
|
158
|
+
const effectiveCaption = item.caption || mirrorEntry?.caption || null;
|
|
159
|
+
const matchedOn = [];
|
|
160
|
+
if (item.filename && item.filename.toLowerCase().includes(normalizedQuery)) {
|
|
161
|
+
matchedOn.push('filename');
|
|
162
|
+
}
|
|
163
|
+
if (effectiveAlt && effectiveAlt.toLowerCase().includes(normalizedQuery)) {
|
|
164
|
+
matchedOn.push('alt');
|
|
165
|
+
}
|
|
166
|
+
if (effectiveCaption && effectiveCaption.toLowerCase().includes(normalizedQuery)) {
|
|
167
|
+
matchedOn.push('caption');
|
|
168
|
+
}
|
|
169
|
+
if (storageKey && storageKey.toLowerCase().includes(normalizedQuery)) {
|
|
170
|
+
matchedOn.push('storageKey');
|
|
171
|
+
}
|
|
172
|
+
if (storageKey) {
|
|
173
|
+
const ref = storageKeyToMarkdownRef(storageKey);
|
|
174
|
+
const stem = ref ? ref.toLowerCase() : '';
|
|
175
|
+
const localStem = mirrorEntry?.localPath
|
|
176
|
+
? path
|
|
177
|
+
.basename(mirrorEntry.localPath)
|
|
178
|
+
.replace(/\.[^.]+$/, '')
|
|
179
|
+
.toLowerCase()
|
|
180
|
+
: '';
|
|
181
|
+
if ((stem && stem.includes(normalizedQuery)) ||
|
|
182
|
+
(localStem && localStem.includes(normalizedQuery))) {
|
|
183
|
+
if (!matchedOn.includes('mirrorStem') && !matchedOn.includes('storageKey')) {
|
|
184
|
+
matchedOn.push('mirrorStem');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (matchedOn.length > 0) {
|
|
189
|
+
matchCount++;
|
|
190
|
+
if (hits.length < limit) {
|
|
191
|
+
hits.push({
|
|
192
|
+
mediaId: item.id.toUpperCase(),
|
|
193
|
+
storageKey,
|
|
194
|
+
filename: item.filename || null,
|
|
195
|
+
mimeType: item.mimeType || null,
|
|
196
|
+
size: typeof item.size === 'number' ? item.size : null,
|
|
197
|
+
alt: effectiveAlt,
|
|
198
|
+
caption: effectiveCaption,
|
|
199
|
+
createdAt: item.createdAt,
|
|
200
|
+
mirrored,
|
|
201
|
+
localPath: mirrorEntry?.localPath ?? null,
|
|
202
|
+
matchedOn,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (page.rawItems.length === 0 || !page.nextCursor) {
|
|
208
|
+
walkComplete = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
cursor = page.nextCursor;
|
|
212
|
+
}
|
|
213
|
+
const truncated = matchCount > hits.length;
|
|
214
|
+
return {
|
|
215
|
+
hits,
|
|
216
|
+
scannedCount: scanned,
|
|
217
|
+
walkComplete,
|
|
218
|
+
totalCount,
|
|
219
|
+
truncated,
|
|
220
|
+
normalizedQuery,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Reports body storageKeys that no local mirror names and that a full remote library walk does not
|
|
225
|
+
* contain. A key is only listed when the walk was complete: an early stop proves nothing about the
|
|
226
|
+
* rest of the library, so an incomplete walk yields nothing. Even a confirmed absence is reported as a
|
|
227
|
+
* lookup result rather than as a missing record — Emit mirrors records and never repairs server data.
|
|
228
|
+
*/
|
|
229
|
+
export function findUnmirroredStorageKeys(referenced, localMirrorKeys, remoteIndex) {
|
|
230
|
+
if (!remoteIndex.complete)
|
|
231
|
+
return [];
|
|
232
|
+
const mirrored = new Set(localMirrorKeys);
|
|
233
|
+
const unresolved = new Set();
|
|
234
|
+
for (const key of referenced) {
|
|
235
|
+
if (mirrored.has(key) || remoteIndex.storageKeys.has(key))
|
|
236
|
+
continue;
|
|
237
|
+
unresolved.add(key);
|
|
238
|
+
}
|
|
239
|
+
return Array.from(unresolved).sort();
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=remote_index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote_index.js","sourceRoot":"","sources":["../../../src/sync/media/remote_index.ts"],"names":[],"mappings":"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"}
|