emdash-emit 0.1.13 → 0.1.15
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 +108 -1
- package/README.md +235 -187
- package/dist/cli/commands/delete.d.ts.map +1 -1
- package/dist/cli/commands/delete.js +2 -0
- package/dist/cli/commands/delete.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +9 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- 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 +108 -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 +15 -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 +24 -5
- package/dist/db/cache.d.ts.map +1 -1
- package/dist/db/cache.js +308 -61
- package/dist/db/cache.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +5 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/sync-delete.d.ts.map +1 -1
- package/dist/mcp/tools/sync-delete.js +5 -0
- package/dist/mcp/tools/sync-delete.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-pull.d.ts.map +1 -1
- package/dist/mcp/tools/sync-pull.js +49 -12
- 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/conflicts.d.ts.map +1 -1
- package/dist/sync/conflicts.js +44 -0
- package/dist/sync/conflicts.js.map +1 -1
- package/dist/sync/delete.d.ts +3 -1
- package/dist/sync/delete.d.ts.map +1 -1
- package/dist/sync/delete.js +7 -20
- 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 +28 -1
- package/dist/sync/doctor.d.ts.map +1 -1
- package/dist/sync/doctor.js +393 -45
- 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 +235 -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 +20 -0
- package/dist/sync/media/companions.d.ts.map +1 -0
- package/dist/sync/media/companions.js +111 -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 +207 -0
- package/dist/sync/media/pull_library.js.map +1 -0
- package/dist/sync/media/remote_index.d.ts +18 -0
- package/dist/sync/media/remote_index.d.ts.map +1 -0
- package/dist/sync/media/remote_index.js +94 -0
- package/dist/sync/media/remote_index.js.map +1 -0
- package/dist/sync/media/types.d.ts +191 -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/merge.d.ts +11 -0
- package/dist/sync/merge.d.ts.map +1 -1
- package/dist/sync/merge.js +53 -6
- package/dist/sync/merge.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 +127 -15
- package/dist/sync/preflight.js.map +1 -1
- package/dist/sync/pull.d.ts +21 -0
- package/dist/sync/pull.d.ts.map +1 -1
- package/dist/sync/pull.js +278 -17
- 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 +135 -58
- 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,273 @@
|
|
|
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 { extractMediaUlid } from '../../client/mapper.js';
|
|
7
|
+
import { parseMarkdown } from '../../utils/frontmatter.js';
|
|
8
|
+
import { collectArticleReferencedMediaIds, collectArticleStorageKeys } from './body_scan.js';
|
|
9
|
+
import { collectLocalMediaMirrorCandidates } from './companions.js';
|
|
10
|
+
import { syncSingleMediaItem } from './mirror_item.js';
|
|
11
|
+
/**
|
|
12
|
+
* Pulls media referenced by a specific article (by ULID or file path).
|
|
13
|
+
*/
|
|
14
|
+
export async function pullArticleMedia(options) {
|
|
15
|
+
const fetchFn = createAuthedFetch(options);
|
|
16
|
+
const collection = options.collection || 'posts';
|
|
17
|
+
const downloadFiles = options.downloadFiles !== false;
|
|
18
|
+
const mediaDir = path.join(options.workspaceRoot, 'media');
|
|
19
|
+
fs.mkdirSync(mediaDir, { recursive: true });
|
|
20
|
+
let articleId = options.article;
|
|
21
|
+
let articleContent = null;
|
|
22
|
+
// 1. Try resolving as direct file path
|
|
23
|
+
const directPath = path.isAbsolute(options.article)
|
|
24
|
+
? options.article
|
|
25
|
+
: path.resolve(options.workspaceRoot, options.article);
|
|
26
|
+
if (fs.existsSync(directPath) && fs.statSync(directPath).isFile()) {
|
|
27
|
+
articleContent = fs.readFileSync(directPath, 'utf8');
|
|
28
|
+
try {
|
|
29
|
+
const parsed = parseMarkdown(articleContent);
|
|
30
|
+
articleId = parsed.frontmatter.id || articleId;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Keep articleId as fallback
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// 2. Try finding file in draft/published
|
|
38
|
+
for (const status of ['draft', 'published']) {
|
|
39
|
+
const statusDir = path.join(options.workspaceRoot, collection, status);
|
|
40
|
+
if (!fs.existsSync(statusDir))
|
|
41
|
+
continue;
|
|
42
|
+
const found = fs
|
|
43
|
+
.readdirSync(statusDir)
|
|
44
|
+
.find((f) => f.endsWith('.md') && f.includes(options.article));
|
|
45
|
+
if (found) {
|
|
46
|
+
articleContent = fs.readFileSync(path.join(statusDir, found), 'utf8');
|
|
47
|
+
try {
|
|
48
|
+
const parsed = parseMarkdown(articleContent);
|
|
49
|
+
articleId = parsed.frontmatter.id || articleId;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// fallback
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// 3. If still absent locally, fetch remote article content from EmDash API
|
|
59
|
+
if (!articleContent) {
|
|
60
|
+
const url = new URL(`/_emdash/api/content/${collection}/${options.article}`, options.origin);
|
|
61
|
+
const res = await fetchFn(url.toString(), {
|
|
62
|
+
headers: {
|
|
63
|
+
Authorization: `Bearer ${options.token}`,
|
|
64
|
+
Accept: 'application/json',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
throw new ApiError({
|
|
69
|
+
message: `Failed to fetch remote article for media resolution: ${res.statusText}`,
|
|
70
|
+
code: 'ARTICLE_NOT_FOUND',
|
|
71
|
+
status: res.status,
|
|
72
|
+
endpoint: url.toString(),
|
|
73
|
+
suggestion: `Verify that article '${options.article}' exists in collection '${collection}' on remote.`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const json = await res.json();
|
|
77
|
+
const data = unwrapEnvelope(json);
|
|
78
|
+
const item = (data.item || data);
|
|
79
|
+
articleId = item.id || options.article;
|
|
80
|
+
// Build a content string from the remote representation so references can be collected even when
|
|
81
|
+
// no local file exists yet.
|
|
82
|
+
const remoteFi = item.featured_image || item.featuredImage;
|
|
83
|
+
const fiId = extractMediaUlid(remoteFi);
|
|
84
|
+
const fiPart = fiId ? `featuredImage: ${fiId}\n` : '';
|
|
85
|
+
const bodyPart = typeof item.content === 'string'
|
|
86
|
+
? item.content
|
|
87
|
+
: JSON.stringify(item.content || item.body || '');
|
|
88
|
+
articleContent = `---\n${fiPart}---\n${bodyPart}`;
|
|
89
|
+
}
|
|
90
|
+
const mediaIds = collectArticleReferencedMediaIds(articleContent);
|
|
91
|
+
// Body storageKeys are the only handle available when a migrated body bakes the R2 `storageKey`
|
|
92
|
+
// instead of the D1 record id. They must never be fed to `GET /_emdash/api/media/{id}` as if they
|
|
93
|
+
// were ids: that lookup keys on the primary key, so it returns 404 for a record that does exist.
|
|
94
|
+
const storageKeyDescriptors = collectArticleStorageKeys(articleContent);
|
|
95
|
+
const descriptorsByKey = new Map();
|
|
96
|
+
for (const descriptor of storageKeyDescriptors) {
|
|
97
|
+
descriptorsByKey.set(descriptor.storageKey, descriptor);
|
|
98
|
+
}
|
|
99
|
+
const allKeys = Array.from(descriptorsByKey.keys()).sort();
|
|
100
|
+
if (mediaIds.length === 0 && allKeys.length === 0) {
|
|
101
|
+
return {
|
|
102
|
+
total: 0,
|
|
103
|
+
downloaded: 0,
|
|
104
|
+
skipped: 0,
|
|
105
|
+
failed: 0,
|
|
106
|
+
articleId,
|
|
107
|
+
mediaIds: [],
|
|
108
|
+
orphanStorageKeys: [],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
let downloadedCount = 0;
|
|
112
|
+
let skippedCount = 0;
|
|
113
|
+
// A body ULID that no canonical lookup resolves is the normal shape of a migrated corpus: the URL
|
|
114
|
+
// carries an R2 storageKey, not a media id. Only a reference that also survives every storageKey
|
|
115
|
+
// check is a problem, so a failed id lookup alone is never reported as `failed`.
|
|
116
|
+
let unresolvedIdCount = 0;
|
|
117
|
+
const recoveredStorageKeys = new Set();
|
|
118
|
+
// Ids whose canonical lookup succeeded. A body key sharing one of these ULIDs is a confirmed match:
|
|
119
|
+
// the record exists, it simply carries a storageKey that differs from the one baked into the body.
|
|
120
|
+
const resolvedRecordIds = new Set();
|
|
121
|
+
// 1. Canonical path: resolve each referenced media record id and mirror it normally.
|
|
122
|
+
for (const mId of mediaIds) {
|
|
123
|
+
try {
|
|
124
|
+
const mediaUrl = new URL(`/_emdash/api/media/${mId}`, options.origin);
|
|
125
|
+
const res = await fetchFn(mediaUrl.toString(), {
|
|
126
|
+
headers: {
|
|
127
|
+
Authorization: `Bearer ${options.token}`,
|
|
128
|
+
Accept: 'application/json',
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
if (!res.ok) {
|
|
132
|
+
unresolvedIdCount++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const json = await res.json();
|
|
136
|
+
const envelope = unwrapEnvelope(json);
|
|
137
|
+
const item = (envelope.item || envelope);
|
|
138
|
+
const result = await syncSingleMediaItem({
|
|
139
|
+
item,
|
|
140
|
+
mediaDir,
|
|
141
|
+
origin: options.origin,
|
|
142
|
+
token: options.token,
|
|
143
|
+
downloadFiles,
|
|
144
|
+
fetchFn,
|
|
145
|
+
cache: options.cache,
|
|
146
|
+
});
|
|
147
|
+
if (result.downloaded)
|
|
148
|
+
downloadedCount++;
|
|
149
|
+
else if (result.skipped)
|
|
150
|
+
skippedCount++;
|
|
151
|
+
resolvedRecordIds.add(mId.toUpperCase());
|
|
152
|
+
if (item.storageKey)
|
|
153
|
+
recoveredStorageKeys.add(item.storageKey);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
unresolvedIdCount++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// 2. Unresolved storageKeys: a body key that no canonical id lookup mirrored is NOT proof of a
|
|
160
|
+
// missing record. The record can exist under a different primary key, so Emit re-checks the key
|
|
161
|
+
// against confirmed evidence only — a record it just resolved under the same ULID, or the local
|
|
162
|
+
// mirror metadata (companion YAML `id` + `storageKey`) — and reports what still fails as
|
|
163
|
+
// unresolved. Emit never fetches such an asset and never registers a record for it.
|
|
164
|
+
const mirrorCandidates = collectLocalMediaMirrorCandidates(mediaDir);
|
|
165
|
+
const verifiedStorageKeys = [];
|
|
166
|
+
const orphanStorageKeys = [];
|
|
167
|
+
for (const key of allKeys) {
|
|
168
|
+
if (recoveredStorageKeys.has(key))
|
|
169
|
+
continue;
|
|
170
|
+
const ulids = (key.match(/[0-9A-HJ-NP-Z]{26}/gi) ?? []).map((u) => u.toUpperCase());
|
|
171
|
+
// Only two things count as evidence: a record Emit successfully fetched under this exact ULID, or
|
|
172
|
+
// confirmed local mirror metadata naming it. A body reference alone is never self-confirming — that
|
|
173
|
+
// is precisely the case where the asset must be reported instead of assumed healthy.
|
|
174
|
+
const confirmed = ulids.some((ulid) => resolvedRecordIds.has(ulid) || mirrorCandidates.has(ulid));
|
|
175
|
+
if (confirmed) {
|
|
176
|
+
verifiedStorageKeys.push(key);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
orphanStorageKeys.push(key);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
total: mediaIds.length + verifiedStorageKeys.length + orphanStorageKeys.length,
|
|
183
|
+
downloaded: downloadedCount,
|
|
184
|
+
skipped: skippedCount,
|
|
185
|
+
failed: orphanStorageKeys.length,
|
|
186
|
+
unresolvedIds: unresolvedIdCount,
|
|
187
|
+
articleId,
|
|
188
|
+
mediaIds,
|
|
189
|
+
verifiedStorageKeys,
|
|
190
|
+
orphanStorageKeys,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Mirrors only the assets a batch of pulled articles actually references, reusing the records the lightweight
|
|
195
|
+
* index walk already fetched instead of paying for a per-asset `GET /_emdash/api/media/{id}`.
|
|
196
|
+
*
|
|
197
|
+
* This is the mirror half of the default-ON local-first pull: the walk phase builds `RemoteStorageKeyIndex`
|
|
198
|
+
* (JSON only, no binaries), and this phase downloads the binary + companion YAML for exactly the storageKeys
|
|
199
|
+
* found in article bodies. A key the walk did not report is NOT proof of a missing record — the record can
|
|
200
|
+
* exist under a different primary key while the body carries an R2 storageKey — so it is re-checked against
|
|
201
|
+
* confirmed evidence only (a walked record resolved under that exact ULID, or local mirror metadata) and what
|
|
202
|
+
* still fails is reported as `orphanStorageKeys`. Emit never fetches such an asset and never registers one.
|
|
203
|
+
*/
|
|
204
|
+
export async function mirrorReferencedAssetsFromIndex(referencedStorageKeys, index, options) {
|
|
205
|
+
const fetchFn = createAuthedFetch(options);
|
|
206
|
+
const downloadFiles = options.downloadFiles !== false;
|
|
207
|
+
const mediaDir = path.join(options.workspaceRoot, 'media');
|
|
208
|
+
fs.mkdirSync(mediaDir, { recursive: true });
|
|
209
|
+
const mirroredStorageKeys = new Set();
|
|
210
|
+
let downloadedCount = 0;
|
|
211
|
+
let skippedCount = 0;
|
|
212
|
+
// Walk every referenced key once. A key resolves to a walked record either directly (the walk reported
|
|
213
|
+
// that exact storageKey) or through a ULID the walk resolved under that record id — the migrated-corpus
|
|
214
|
+
// case where the body URL bakes an R2 storageKey that differs from the record's own primary key.
|
|
215
|
+
const referenced = [...referencedStorageKeys];
|
|
216
|
+
const mirrorCandidates = collectLocalMediaMirrorCandidates(mediaDir);
|
|
217
|
+
const verifiedStorageKeys = [];
|
|
218
|
+
const orphanStorageKeys = [];
|
|
219
|
+
for (const key of referenced) {
|
|
220
|
+
let item = index.recordsByStorageKey.get(key);
|
|
221
|
+
if (!item) {
|
|
222
|
+
// The body key may itself be a record id the walk stored under `recordsById`, or share a ULID with a
|
|
223
|
+
// walked record. Reuse that record rather than re-fetching it.
|
|
224
|
+
const ulids = (key.match(/[0-9A-HJ-NP-Z]{26}/gi) ?? []).map((u) => u.toUpperCase());
|
|
225
|
+
for (const ulid of ulids) {
|
|
226
|
+
const byId = index.recordsById.get(ulid);
|
|
227
|
+
if (byId) {
|
|
228
|
+
item = byId;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (item) {
|
|
234
|
+
const result = await syncSingleMediaItem({
|
|
235
|
+
item,
|
|
236
|
+
mediaDir,
|
|
237
|
+
origin: options.origin,
|
|
238
|
+
token: options.token,
|
|
239
|
+
downloadFiles,
|
|
240
|
+
fetchFn,
|
|
241
|
+
cache: options.cache,
|
|
242
|
+
});
|
|
243
|
+
if (result.downloaded)
|
|
244
|
+
downloadedCount++;
|
|
245
|
+
else if (result.skipped)
|
|
246
|
+
skippedCount++;
|
|
247
|
+
if (item.storageKey)
|
|
248
|
+
mirroredStorageKeys.add(item.storageKey);
|
|
249
|
+
mirroredStorageKeys.add(key);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// No walked record owns this key. Only two things count as evidence it is healthy: a local mirror
|
|
253
|
+
// companion already names the ULID, or the walk stored a matching storageKey. A bare body reference is
|
|
254
|
+
// never self-confirming — that is precisely the case to report rather than assume healthy.
|
|
255
|
+
const ulids = (key.match(/[0-9A-HJ-NP-Z]{26}/gi) ?? []).map((u) => u.toUpperCase());
|
|
256
|
+
const confirmed = index.storageKeys.has(key) ||
|
|
257
|
+
ulids.some((ulid) => index.recordsById.has(ulid) || mirrorCandidates.has(ulid));
|
|
258
|
+
if (confirmed) {
|
|
259
|
+
verifiedStorageKeys.push(key);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
orphanStorageKeys.push(key);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
mirroredStorageKeys,
|
|
267
|
+
downloaded: downloadedCount,
|
|
268
|
+
skipped: skippedCount,
|
|
269
|
+
verifiedStorageKeys,
|
|
270
|
+
orphanStorageKeys,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=pull_article.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull_article.js","sourceRoot":"","sources":["../../../src/sync/media/pull_article.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type PullMediaOptions, type PullMediaResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pulls media metadata and files from EmDash API into media/ directory with cursor pagination.
|
|
4
|
+
* media/<markdownRef>.yaml
|
|
5
|
+
* media/<markdownRef>.<ext>
|
|
6
|
+
*
|
|
7
|
+
* `markdownRef` is the sanitized storageKey basename (`storageKeyToMarkdownRef`), never the record id, so the
|
|
8
|
+
* mirror on disk shares one stem with the body URL that references it; the original `filename` survives only
|
|
9
|
+
* inside the companion YAML. See `mediaMirrorNames()` for the full naming and collision rule.
|
|
10
|
+
*/
|
|
11
|
+
export declare function pullMedia(options: PullMediaOptions): Promise<PullMediaResult>;
|
|
12
|
+
//# sourceMappingURL=pull_library.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull_library.d.ts","sourceRoot":"","sources":["../../../src/sync/media/pull_library.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EAErB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAuMnF"}
|
|
@@ -0,0 +1,207 @@
|
|
|
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 { 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.article) {
|
|
25
|
+
return pullArticleMedia({
|
|
26
|
+
workspaceRoot: options.workspaceRoot,
|
|
27
|
+
origin: options.origin,
|
|
28
|
+
token: options.token,
|
|
29
|
+
refreshToken: options.refreshToken,
|
|
30
|
+
article: options.article,
|
|
31
|
+
collection: options.collection,
|
|
32
|
+
downloadFiles: options.downloadFiles,
|
|
33
|
+
fetchFn: options.fetchFn,
|
|
34
|
+
cache: options.cache,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const fetchFn = createAuthedFetch(options);
|
|
38
|
+
const targetTotal = options.all ? Infinity : (options.limit ?? DEFAULT_CONFIG.mediaSyncLimit);
|
|
39
|
+
const downloadFiles = options.downloadFiles !== false;
|
|
40
|
+
const batchSize = MEDIA_LIST_PAGE_SIZE;
|
|
41
|
+
const mediaDir = path.join(options.workspaceRoot, 'media');
|
|
42
|
+
fs.mkdirSync(mediaDir, { recursive: true });
|
|
43
|
+
// Every mirrored companion is a confirmed storageKey source, including the ones written before Emit
|
|
44
|
+
// persisted the field, whose `url:` still names the key. Seeding from disk means `--prune` never
|
|
45
|
+
// deletes an asset that this run's media window simply did not reach.
|
|
46
|
+
const activeStorageKeys = new Set();
|
|
47
|
+
for (const candidate of collectLocalMediaMirrorCandidates(mediaDir).values()) {
|
|
48
|
+
if (candidate.storageKey)
|
|
49
|
+
activeStorageKeys.add(candidate.storageKey);
|
|
50
|
+
}
|
|
51
|
+
// Metadata for the coverage report and the storageKey census. `pullMedia` already walks the library
|
|
52
|
+
// cursor-by-cursor, so it owns the index instead of paying for a second one.
|
|
53
|
+
const remoteIndex = {
|
|
54
|
+
storageKeys: activeStorageKeys,
|
|
55
|
+
mediaIdsByStorageKey: new Map(),
|
|
56
|
+
recordsByStorageKey: new Map(),
|
|
57
|
+
recordsById: new Map(),
|
|
58
|
+
totalCount: null,
|
|
59
|
+
complete: false,
|
|
60
|
+
scannedCount: 0,
|
|
61
|
+
};
|
|
62
|
+
const activeMediaBaseNames = new Set();
|
|
63
|
+
// Only records the remote walk actually reports may authorise a mirror to stay. Seeding this from local
|
|
64
|
+
// companions instead would make every existing file self-exempt and prune nothing at all.
|
|
65
|
+
const activeMediaIds = new Set();
|
|
66
|
+
let totalProcessed = 0;
|
|
67
|
+
let downloadedCount = 0;
|
|
68
|
+
let skippedCount = 0;
|
|
69
|
+
let cursor = undefined;
|
|
70
|
+
while (totalProcessed < targetTotal) {
|
|
71
|
+
const remaining = targetTotal - totalProcessed;
|
|
72
|
+
const currentBatch = Math.min(remaining, batchSize);
|
|
73
|
+
const url = new URL(`/_emdash/api/media`, options.origin);
|
|
74
|
+
url.searchParams.set('limit', String(currentBatch));
|
|
75
|
+
if (cursor) {
|
|
76
|
+
url.searchParams.set('cursor', cursor);
|
|
77
|
+
}
|
|
78
|
+
const res = await fetchFn(url.toString(), {
|
|
79
|
+
method: 'GET',
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: `Bearer ${options.token}`,
|
|
82
|
+
Accept: 'application/json',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
throw new ApiError({
|
|
87
|
+
message: `Failed to list media: ${res.statusText}`,
|
|
88
|
+
code: 'MEDIA_LIST_FAILED',
|
|
89
|
+
status: res.status,
|
|
90
|
+
endpoint: url.toString(),
|
|
91
|
+
suggestion: 'Verify network connection and token permissions.',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const json = await res.json();
|
|
95
|
+
const data = unwrapEnvelope(json);
|
|
96
|
+
if (typeof data.totalCount === 'number')
|
|
97
|
+
remoteIndex.totalCount = data.totalCount;
|
|
98
|
+
const rawItems = (Array.isArray(data.items) ? data.items : Array.isArray(data) ? data : []);
|
|
99
|
+
if (rawItems.length === 0) {
|
|
100
|
+
// An empty page ends the listing regardless of any cursor the server echoes back.
|
|
101
|
+
remoteIndex.complete = true;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
for (const item of rawItems) {
|
|
105
|
+
const names = mediaMirrorNames(item);
|
|
106
|
+
activeMediaBaseNames.add(names.markdownRef);
|
|
107
|
+
for (const legacy of names.legacyBaseNames) {
|
|
108
|
+
activeMediaBaseNames.add(legacy);
|
|
109
|
+
}
|
|
110
|
+
activeMediaIds.add(item.id);
|
|
111
|
+
if (item.storageKey) {
|
|
112
|
+
activeStorageKeys.add(item.storageKey);
|
|
113
|
+
remoteIndex.mediaIdsByStorageKey.set(item.storageKey, item.id);
|
|
114
|
+
}
|
|
115
|
+
const result = await syncSingleMediaItem({
|
|
116
|
+
item,
|
|
117
|
+
mediaDir,
|
|
118
|
+
origin: options.origin,
|
|
119
|
+
token: options.token,
|
|
120
|
+
downloadFiles,
|
|
121
|
+
fetchFn,
|
|
122
|
+
cache: options.cache,
|
|
123
|
+
});
|
|
124
|
+
if (result.downloaded)
|
|
125
|
+
downloadedCount++;
|
|
126
|
+
else if (result.skipped)
|
|
127
|
+
skippedCount++;
|
|
128
|
+
totalProcessed++;
|
|
129
|
+
remoteIndex.scannedCount++;
|
|
130
|
+
if (totalProcessed >= targetTotal) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!data.nextCursor || typeof data.nextCursor !== 'string') {
|
|
135
|
+
remoteIndex.complete = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
cursor = data.nextCursor;
|
|
139
|
+
}
|
|
140
|
+
// Pruning logic for media: if --prune or --all is enabled, delete local media not present on remote
|
|
141
|
+
let deletedCount = 0;
|
|
142
|
+
if ((options.prune || options.all) && fs.existsSync(mediaDir)) {
|
|
143
|
+
const files = fs.readdirSync(mediaDir);
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
// Leftover `legacy_*` mirrors from the removed file-route fallback are never matched by the
|
|
146
|
+
// mediaId pattern below, so they survive pruning and must be deleted by hand.
|
|
147
|
+
if (file.startsWith('legacy_'))
|
|
148
|
+
continue;
|
|
149
|
+
// A mirror is identified with its companion YAML: the stem names the storageKey, and the record id
|
|
150
|
+
// lives inside the companion. Only a file Emit can attribute to a record may be pruned — an
|
|
151
|
+
// unattributable migrated name is kept, because deleting an asset over a guess is unrecoverable.
|
|
152
|
+
const stem = file.replace(/\.[^.]+$/, '');
|
|
153
|
+
if (activeMediaBaseNames.has(stem) || activeMediaBaseNames.has(stem.replace(/^\d{8}_/, ''))) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const companionId = mediaRecordIdForStem(mediaDir, stem);
|
|
157
|
+
if (companionId && !activeMediaIds.has(companionId)) {
|
|
158
|
+
try {
|
|
159
|
+
fs.unlinkSync(path.join(mediaDir, file));
|
|
160
|
+
deletedCount++;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Ignore unlink errors
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// A library-wide walk is exhaustive, so it doubles as the storageKey census: every article reference
|
|
169
|
+
// that neither a local companion nor any remote record names is reported. The list endpoint exposes no
|
|
170
|
+
// storageKey filter, which is why the census requires the full walk `--all` already performs.
|
|
171
|
+
let referencedUnmirroredStorageKeys = undefined;
|
|
172
|
+
if (options.all) {
|
|
173
|
+
const referenced = new Set();
|
|
174
|
+
for (const status of ['draft', 'published']) {
|
|
175
|
+
const statusDir = path.join(options.workspaceRoot, options.collection || 'posts', status);
|
|
176
|
+
if (!fs.existsSync(statusDir))
|
|
177
|
+
continue;
|
|
178
|
+
for (const file of fs.readdirSync(statusDir)) {
|
|
179
|
+
if (!file.endsWith('.md'))
|
|
180
|
+
continue;
|
|
181
|
+
try {
|
|
182
|
+
const content = fs.readFileSync(path.join(statusDir, file), 'utf8');
|
|
183
|
+
for (const descriptor of collectArticleStorageKeys(content)) {
|
|
184
|
+
referenced.add(descriptor.storageKey);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// An unreadable article contributes nothing to the census.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (referenced.size > 0) {
|
|
193
|
+
referencedUnmirroredStorageKeys = findUnmirroredStorageKeys(referenced, activeStorageKeys, remoteIndex);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
total: totalProcessed,
|
|
198
|
+
downloaded: downloadedCount,
|
|
199
|
+
skipped: skippedCount,
|
|
200
|
+
deleted: deletedCount,
|
|
201
|
+
remoteTotalCount: remoteIndex.complete
|
|
202
|
+
? (remoteIndex.totalCount ?? remoteIndex.scannedCount)
|
|
203
|
+
: remoteIndex.totalCount,
|
|
204
|
+
referencedUnmirroredStorageKeys,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
//# 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,18 @@
|
|
|
1
|
+
import { type RemoteStorageKeyIndex } from './types.js';
|
|
2
|
+
export declare function collectRemoteStorageKeys(options: {
|
|
3
|
+
workspaceRoot: string;
|
|
4
|
+
origin: string;
|
|
5
|
+
token: string;
|
|
6
|
+
refreshToken?: string;
|
|
7
|
+
/** Stop after this many records. Defaults to unlimited, which is the whole library. */
|
|
8
|
+
maxItems?: number;
|
|
9
|
+
fetchFn?: typeof fetch;
|
|
10
|
+
}): Promise<RemoteStorageKeyIndex>;
|
|
11
|
+
/**
|
|
12
|
+
* Reports body storageKeys that no local mirror names and that a full remote library walk does not
|
|
13
|
+
* contain. A key is only listed when the walk was complete: an early stop proves nothing about the
|
|
14
|
+
* rest of the library, so an incomplete walk yields nothing. Even a confirmed absence is reported as a
|
|
15
|
+
* lookup result rather than as a missing record — Emit mirrors records and never repairs server data.
|
|
16
|
+
*/
|
|
17
|
+
export declare function findUnmirroredStorageKeys(referenced: Iterable<string>, localMirrorKeys: Iterable<string>, remoteIndex: RemoteStorageKeyIndex): string[];
|
|
18
|
+
//# 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":"AAGA,OAAO,EAA8C,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEpG,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,CA6EjC;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,94 @@
|
|
|
1
|
+
import { unwrapEnvelope } from '../../client/api.js';
|
|
2
|
+
import { ApiError } from '../../client/error.js';
|
|
3
|
+
import { createAuthedFetch } from '../../client/http.js';
|
|
4
|
+
import { MEDIA_LIST_PAGE_SIZE } from './types.js';
|
|
5
|
+
export async function collectRemoteStorageKeys(options) {
|
|
6
|
+
const fetchFn = createAuthedFetch(options);
|
|
7
|
+
const batchSize = MEDIA_LIST_PAGE_SIZE;
|
|
8
|
+
const maxItems = options.maxItems ?? Infinity;
|
|
9
|
+
const storageKeys = new Set();
|
|
10
|
+
const mediaIdsByStorageKey = new Map();
|
|
11
|
+
const recordsByStorageKey = new Map();
|
|
12
|
+
const recordsById = new Map();
|
|
13
|
+
let totalCount = null;
|
|
14
|
+
let scanned = 0;
|
|
15
|
+
let complete = false;
|
|
16
|
+
let cursor = undefined;
|
|
17
|
+
for (;;) {
|
|
18
|
+
const remaining = maxItems - scanned;
|
|
19
|
+
if (remaining <= 0)
|
|
20
|
+
break;
|
|
21
|
+
const currentBatch = Math.min(remaining, batchSize);
|
|
22
|
+
const url = new URL('/_emdash/api/media', options.origin);
|
|
23
|
+
url.searchParams.set('limit', String(currentBatch));
|
|
24
|
+
if (cursor)
|
|
25
|
+
url.searchParams.set('cursor', cursor);
|
|
26
|
+
const res = await fetchFn(url.toString(), {
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${options.token}`,
|
|
29
|
+
Accept: 'application/json',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new ApiError({
|
|
34
|
+
message: `Failed to list media: ${res.statusText}`,
|
|
35
|
+
code: 'MEDIA_LIST_FAILED',
|
|
36
|
+
status: res.status,
|
|
37
|
+
endpoint: url.toString(),
|
|
38
|
+
suggestion: 'Verify network connection and token permissions.',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const data = unwrapEnvelope(await res.json());
|
|
42
|
+
if (typeof data.totalCount === 'number')
|
|
43
|
+
totalCount = data.totalCount;
|
|
44
|
+
const rawItems = (Array.isArray(data.items) ? data.items : Array.isArray(data.data) ? data.data : []);
|
|
45
|
+
for (const item of rawItems) {
|
|
46
|
+
scanned++;
|
|
47
|
+
if (item.id)
|
|
48
|
+
recordsById.set(item.id.toUpperCase(), item);
|
|
49
|
+
if (typeof item.storageKey === 'string' && item.storageKey) {
|
|
50
|
+
storageKeys.add(item.storageKey);
|
|
51
|
+
// First record wins a repeated key, so reporting a match stays deterministic.
|
|
52
|
+
if (item.id && !mediaIdsByStorageKey.has(item.storageKey)) {
|
|
53
|
+
mediaIdsByStorageKey.set(item.storageKey, item.id);
|
|
54
|
+
}
|
|
55
|
+
if (!recordsByStorageKey.has(item.storageKey)) {
|
|
56
|
+
recordsByStorageKey.set(item.storageKey, item);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (rawItems.length === 0 || typeof data.nextCursor !== 'string' || !data.nextCursor) {
|
|
61
|
+
complete = true;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
cursor = data.nextCursor;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
storageKeys,
|
|
68
|
+
mediaIdsByStorageKey,
|
|
69
|
+
recordsByStorageKey,
|
|
70
|
+
recordsById,
|
|
71
|
+
totalCount,
|
|
72
|
+
complete,
|
|
73
|
+
scannedCount: scanned,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Reports body storageKeys that no local mirror names and that a full remote library walk does not
|
|
78
|
+
* contain. A key is only listed when the walk was complete: an early stop proves nothing about the
|
|
79
|
+
* rest of the library, so an incomplete walk yields nothing. Even a confirmed absence is reported as a
|
|
80
|
+
* lookup result rather than as a missing record — Emit mirrors records and never repairs server data.
|
|
81
|
+
*/
|
|
82
|
+
export function findUnmirroredStorageKeys(referenced, localMirrorKeys, remoteIndex) {
|
|
83
|
+
if (!remoteIndex.complete)
|
|
84
|
+
return [];
|
|
85
|
+
const mirrored = new Set(localMirrorKeys);
|
|
86
|
+
const unresolved = new Set();
|
|
87
|
+
for (const key of referenced) {
|
|
88
|
+
if (mirrored.has(key) || remoteIndex.storageKeys.has(key))
|
|
89
|
+
continue;
|
|
90
|
+
unresolved.add(key);
|
|
91
|
+
}
|
|
92
|
+
return Array.from(unresolved).sort();
|
|
93
|
+
}
|
|
94
|
+
//# 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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"}
|