emdash-emit 0.1.14 → 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.
Files changed (133) hide show
  1. package/CHANGELOG.md +77 -1
  2. package/README.md +235 -187
  3. package/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/dist/cli/commands/doctor.js +6 -0
  5. package/dist/cli/commands/doctor.js.map +1 -1
  6. package/dist/cli/commands/pull.d.ts +32 -0
  7. package/dist/cli/commands/pull.d.ts.map +1 -1
  8. package/dist/cli/commands/pull.js +108 -9
  9. package/dist/cli/commands/pull.js.map +1 -1
  10. package/dist/cli/commands/push.d.ts.map +1 -1
  11. package/dist/cli/commands/push.js +18 -0
  12. package/dist/cli/commands/push.js.map +1 -1
  13. package/dist/cli/commands/status.d.ts.map +1 -1
  14. package/dist/cli/commands/status.js +15 -5
  15. package/dist/cli/commands/status.js.map +1 -1
  16. package/dist/client/media.d.ts.map +1 -1
  17. package/dist/client/media.js +16 -5
  18. package/dist/client/media.js.map +1 -1
  19. package/dist/db/cache.d.ts +19 -4
  20. package/dist/db/cache.d.ts.map +1 -1
  21. package/dist/db/cache.js +127 -16
  22. package/dist/db/cache.js.map +1 -1
  23. package/dist/mcp/server.d.ts.map +1 -1
  24. package/dist/mcp/server.js +5 -3
  25. package/dist/mcp/server.js.map +1 -1
  26. package/dist/mcp/tools/sync-doctor.d.ts.map +1 -1
  27. package/dist/mcp/tools/sync-doctor.js +12 -0
  28. package/dist/mcp/tools/sync-doctor.js.map +1 -1
  29. package/dist/mcp/tools/sync-pull.d.ts.map +1 -1
  30. package/dist/mcp/tools/sync-pull.js +49 -12
  31. package/dist/mcp/tools/sync-pull.js.map +1 -1
  32. package/dist/mcp/tools/sync-push.d.ts.map +1 -1
  33. package/dist/mcp/tools/sync-push.js +5 -0
  34. package/dist/mcp/tools/sync-push.js.map +1 -1
  35. package/dist/mcp/tools/sync-status.d.ts.map +1 -1
  36. package/dist/mcp/tools/sync-status.js +6 -2
  37. package/dist/mcp/tools/sync-status.js.map +1 -1
  38. package/dist/sync/delete.d.ts +2 -1
  39. package/dist/sync/delete.d.ts.map +1 -1
  40. package/dist/sync/delete.js +6 -19
  41. package/dist/sync/delete.js.map +1 -1
  42. package/dist/sync/diff.d.ts.map +1 -1
  43. package/dist/sync/diff.js +18 -2
  44. package/dist/sync/diff.js.map +1 -1
  45. package/dist/sync/doctor.d.ts +27 -1
  46. package/dist/sync/doctor.d.ts.map +1 -1
  47. package/dist/sync/doctor.js +309 -26
  48. package/dist/sync/doctor.js.map +1 -1
  49. package/dist/sync/guards.d.ts +45 -0
  50. package/dist/sync/guards.d.ts.map +1 -0
  51. package/dist/sync/guards.js +235 -0
  52. package/dist/sync/guards.js.map +1 -0
  53. package/dist/sync/media/body_scan.d.ts +15 -0
  54. package/dist/sync/media/body_scan.d.ts.map +1 -0
  55. package/dist/sync/media/body_scan.js +132 -0
  56. package/dist/sync/media/body_scan.js.map +1 -0
  57. package/dist/sync/media/companions.d.ts +20 -0
  58. package/dist/sync/media/companions.d.ts.map +1 -0
  59. package/dist/sync/media/companions.js +111 -0
  60. package/dist/sync/media/companions.js.map +1 -0
  61. package/dist/sync/media/index.d.ts +9 -0
  62. package/dist/sync/media/index.d.ts.map +1 -0
  63. package/dist/sync/media/index.js +9 -0
  64. package/dist/sync/media/index.js.map +1 -0
  65. package/dist/sync/media/mirror_item.d.ts +27 -0
  66. package/dist/sync/media/mirror_item.d.ts.map +1 -0
  67. package/dist/sync/media/mirror_item.js +207 -0
  68. package/dist/sync/media/mirror_item.js.map +1 -0
  69. package/dist/sync/media/naming_mirror.d.ts +36 -0
  70. package/dist/sync/media/naming_mirror.d.ts.map +1 -0
  71. package/dist/sync/media/naming_mirror.js +143 -0
  72. package/dist/sync/media/naming_mirror.js.map +1 -0
  73. package/dist/sync/media/pull_article.d.ts +37 -0
  74. package/dist/sync/media/pull_article.d.ts.map +1 -0
  75. package/dist/sync/media/pull_article.js +273 -0
  76. package/dist/sync/media/pull_article.js.map +1 -0
  77. package/dist/sync/media/pull_library.d.ts +12 -0
  78. package/dist/sync/media/pull_library.d.ts.map +1 -0
  79. package/dist/sync/media/pull_library.js +207 -0
  80. package/dist/sync/media/pull_library.js.map +1 -0
  81. package/dist/sync/media/remote_index.d.ts +18 -0
  82. package/dist/sync/media/remote_index.d.ts.map +1 -0
  83. package/dist/sync/media/remote_index.js +94 -0
  84. package/dist/sync/media/remote_index.js.map +1 -0
  85. package/dist/sync/media/types.d.ts +191 -0
  86. package/dist/sync/media/types.d.ts.map +1 -0
  87. package/dist/sync/media/types.js +11 -0
  88. package/dist/sync/media/types.js.map +1 -0
  89. package/dist/sync/media.d.ts +1 -74
  90. package/dist/sync/media.d.ts.map +1 -1
  91. package/dist/sync/media.js +1 -592
  92. package/dist/sync/media.js.map +1 -1
  93. package/dist/sync/orchestrator.d.ts +23 -0
  94. package/dist/sync/orchestrator.d.ts.map +1 -1
  95. package/dist/sync/orchestrator.js +85 -1
  96. package/dist/sync/orchestrator.js.map +1 -1
  97. package/dist/sync/preflight.d.ts +1 -1
  98. package/dist/sync/preflight.d.ts.map +1 -1
  99. package/dist/sync/preflight.js +127 -15
  100. package/dist/sync/preflight.js.map +1 -1
  101. package/dist/sync/pull.d.ts +19 -0
  102. package/dist/sync/pull.d.ts.map +1 -1
  103. package/dist/sync/pull.js +169 -15
  104. package/dist/sync/pull.js.map +1 -1
  105. package/dist/sync/push.d.ts +7 -0
  106. package/dist/sync/push.d.ts.map +1 -1
  107. package/dist/sync/push.js +37 -10
  108. package/dist/sync/push.js.map +1 -1
  109. package/dist/sync/status.d.ts +4 -0
  110. package/dist/sync/status.d.ts.map +1 -1
  111. package/dist/sync/status.js +68 -4
  112. package/dist/sync/status.js.map +1 -1
  113. package/dist/utils/config.d.ts +25 -0
  114. package/dist/utils/config.d.ts.map +1 -1
  115. package/dist/utils/config.js +28 -2
  116. package/dist/utils/config.js.map +1 -1
  117. package/dist/utils/frontmatter.d.ts +1 -1
  118. package/dist/utils/frontmatter.d.ts.map +1 -1
  119. package/dist/utils/frontmatter.js +15 -4
  120. package/dist/utils/frontmatter.js.map +1 -1
  121. package/dist/utils/media-body-refs.d.ts +81 -0
  122. package/dist/utils/media-body-refs.d.ts.map +1 -0
  123. package/dist/utils/media-body-refs.js +240 -0
  124. package/dist/utils/media-body-refs.js.map +1 -0
  125. package/dist/utils/media-refs.d.ts +44 -0
  126. package/dist/utils/media-refs.d.ts.map +1 -0
  127. package/dist/utils/media-refs.js +101 -0
  128. package/dist/utils/media-refs.js.map +1 -0
  129. package/dist/utils/naming.d.ts +31 -0
  130. package/dist/utils/naming.d.ts.map +1 -1
  131. package/dist/utils/naming.js +58 -0
  132. package/dist/utils/naming.js.map +1 -1
  133. package/package.json +1 -1
@@ -0,0 +1,207 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import yaml from 'js-yaml';
4
+ import { MEDIA_FILE_ROUTE_PREFIX } from '../../utils/media-refs.js';
5
+ import { buildMediaMetaDoc } from './companions.js';
6
+ import { cleanupDuplicateMediaFilesForId, mediaMirrorNames, moveFileSafe, } from './naming_mirror.js';
7
+ /**
8
+ * Persists YAML companion metadata and downloads the binary file for a single media item.
9
+ */
10
+ export async function syncSingleMediaItem(options) {
11
+ const { item, mediaDir, origin, token, downloadFiles, fetchFn, cache } = options;
12
+ const names = mediaMirrorNames(item, mediaDir);
13
+ const baseName = names.markdownRef;
14
+ const yamlPath = path.join(mediaDir, `${baseName}.yaml`);
15
+ // Read any existing companion for this record id first, whatever stem it currently occupies. The
16
+ // storageKey-named companion is the expected location; the legacy stems are where an older workspace still
17
+ // holds the same asset. `legacy_<storageKey>.yaml` is skipped unconditionally: it carries a matching ULID
18
+ // but fabricates its metadata from a file route, so it must never seed a real record.
19
+ let existingMeta = null;
20
+ let existingMetaPath = null;
21
+ const mediaFiles = fs.readdirSync(mediaDir);
22
+ const companionCandidates = [yamlPath];
23
+ for (const legacy of names.legacyBaseNames) {
24
+ companionCandidates.push(path.join(mediaDir, `${legacy}.yaml`));
25
+ }
26
+ for (const candidate of companionCandidates) {
27
+ if (!fs.existsSync(candidate))
28
+ continue;
29
+ try {
30
+ existingMeta = yaml.load(fs.readFileSync(candidate, 'utf8'));
31
+ existingMetaPath = candidate;
32
+ break;
33
+ }
34
+ catch {
35
+ existingMeta = null;
36
+ }
37
+ }
38
+ if (!existingMeta) {
39
+ const matchingYaml = mediaFiles.find((f) => f.endsWith('.yaml') &&
40
+ !f.startsWith('legacy_') &&
41
+ f !== path.basename(yamlPath) &&
42
+ (f.includes(`_${item.id}-`) || f.startsWith(`${item.id}-`)));
43
+ if (matchingYaml) {
44
+ try {
45
+ existingMeta = yaml.load(fs.readFileSync(path.join(mediaDir, matchingYaml), 'utf8'));
46
+ existingMetaPath = path.join(mediaDir, matchingYaml);
47
+ }
48
+ catch {
49
+ existingMeta = null;
50
+ }
51
+ }
52
+ }
53
+ // 1. Write YAML metadata under the storageKey-derived stem
54
+ const metaDoc = buildMediaMetaDoc(item, origin, existingMeta);
55
+ fs.writeFileSync(yamlPath, yaml.dump(metaDoc, { indent: 2, quotingType: '"', forceQuotes: false, lineWidth: -1 }), 'utf8');
56
+ let downloaded = false;
57
+ let skipped = false;
58
+ // 2. Download binary file if enabled and url is present
59
+ const canonicalDownloadPath = item.url || (item.storageKey ? `${MEDIA_FILE_ROUTE_PREFIX}${item.storageKey}` : '');
60
+ const fileExt = names.ext || (item.mimeType.includes('png') ? '.png' : '.jpg');
61
+ const targetFilePath = path.join(mediaDir, `${baseName}${fileExt}`);
62
+ // Rename an existing mirror into the new name before considering a download, so migrating a workspace
63
+ // costs no bandwidth. Size must match: a same-stem file of a different size is not this asset.
64
+ if (!fs.existsSync(targetFilePath)) {
65
+ const candidates = names.legacyBaseNames
66
+ .map((legacy) => path.join(mediaDir, `${legacy}${fileExt}`))
67
+ .concat(mediaFiles
68
+ .filter((f) => !f.endsWith('.yaml') &&
69
+ !f.startsWith('legacy_') &&
70
+ (f.includes(`_${item.id}-`) || f.startsWith(`${item.id}-`)))
71
+ .map((f) => path.join(mediaDir, f)));
72
+ for (const candidate of candidates) {
73
+ if (candidate === targetFilePath || !fs.existsSync(candidate))
74
+ continue;
75
+ try {
76
+ const stat = fs.statSync(candidate);
77
+ if (stat.size === item.size) {
78
+ moveFileSafe(candidate, targetFilePath);
79
+ break;
80
+ }
81
+ }
82
+ catch { }
83
+ }
84
+ }
85
+ if (downloadFiles && canonicalDownloadPath) {
86
+ let shouldDownload = true;
87
+ if (fs.existsSync(targetFilePath)) {
88
+ const stat = fs.statSync(targetFilePath);
89
+ if (stat.size === item.size) {
90
+ shouldDownload = false;
91
+ skipped = true;
92
+ }
93
+ }
94
+ if (shouldDownload) {
95
+ try {
96
+ const downloadUrl = new URL(canonicalDownloadPath, origin).toString();
97
+ const fileRes = await fetchFn(downloadUrl, {
98
+ headers: {
99
+ Authorization: `Bearer ${token}`,
100
+ },
101
+ });
102
+ if (fileRes.ok) {
103
+ const buffer = Buffer.from(await fileRes.arrayBuffer());
104
+ fs.writeFileSync(targetFilePath, buffer);
105
+ downloaded = true;
106
+ }
107
+ }
108
+ catch {
109
+ // Ignore individual download failures
110
+ }
111
+ }
112
+ }
113
+ // Register the mirrored asset in media_state. Before storageKey naming, only the push/upload path wrote
114
+ // this table, so a pulled workspace had no index at all and every lookup fell back to scanning YAML. With
115
+ // the mirror name no longer carrying the record id, that index is what makes a body reference resolvable
116
+ // without re-reading thousands of companions.
117
+ if (cache && item.storageKey) {
118
+ const mirrorUrl = typeof metaDoc.url === 'string' && metaDoc.url
119
+ ? metaDoc.url
120
+ : `${MEDIA_FILE_ROUTE_PREFIX}${item.storageKey}`;
121
+ cache.saveMediaState({
122
+ localHash: `media-record:${item.id}`,
123
+ mediaId: item.id,
124
+ storageKey: item.storageKey,
125
+ url: mirrorUrl,
126
+ workspacePath: path.posix.join('media', `${baseName}${fileExt}`),
127
+ markdownRef: path.posix.join('media', baseName),
128
+ status: 'ready',
129
+ });
130
+ }
131
+ // Drop the companion this asset occupied under a legacy stem. Done after the binary rename so an
132
+ // interrupted run never leaves a record whose metadata and bytes disagree about the name.
133
+ if (existingMetaPath && existingMetaPath !== yamlPath && fs.existsSync(existingMetaPath)) {
134
+ try {
135
+ fs.unlinkSync(existingMetaPath);
136
+ }
137
+ catch { }
138
+ }
139
+ for (const legacy of names.legacyBaseNames) {
140
+ const legacyYaml = path.join(mediaDir, `${legacy}.yaml`);
141
+ if (legacyYaml !== yamlPath && fs.existsSync(legacyYaml)) {
142
+ try {
143
+ fs.unlinkSync(legacyYaml);
144
+ }
145
+ catch { }
146
+ }
147
+ }
148
+ // Clean up any duplicate files with the same media ID but a different datePrefix/baseName
149
+ cleanupDuplicateMediaFilesForId(mediaDir, item.id, baseName);
150
+ return { downloaded, skipped, baseName };
151
+ }
152
+ /**
153
+ * Promotes a local draft image from media/drafts/<name> into the canonical
154
+ * media/<markdownRef>.<ext> location alongside its matching media/<markdownRef>.yaml companion, where
155
+ * markdownRef is derived from the storageKey the article body will reference. Returns both the workspace-relative
156
+ * path of the promoted binary and the shared `media/<markdownRef>` stem, so the caller can record the mirror in
157
+ * `media_state.markdown_ref`.
158
+ */
159
+ export function promoteDraftMediaFile(options) {
160
+ const { workspaceRoot, origin, draftAbsPath, item, removeDraft = true } = options;
161
+ const mediaDir = path.join(workspaceRoot, 'media');
162
+ fs.mkdirSync(mediaDir, { recursive: true });
163
+ const ext = path.extname(item.filename) ||
164
+ path.extname(draftAbsPath) ||
165
+ (item.mimeType.includes('png') ? '.png' : '.jpg');
166
+ const names = mediaMirrorNames({ ...item, filename: item.filename || path.basename(draftAbsPath) }, mediaDir);
167
+ const baseName = names.markdownRef;
168
+ const yamlPath = path.join(mediaDir, `${baseName}.yaml`);
169
+ let existingMeta = null;
170
+ if (fs.existsSync(yamlPath)) {
171
+ try {
172
+ existingMeta = yaml.load(fs.readFileSync(yamlPath, 'utf8'));
173
+ }
174
+ catch {
175
+ existingMeta = null;
176
+ }
177
+ }
178
+ const metaDoc = buildMediaMetaDoc(item, origin, existingMeta);
179
+ fs.writeFileSync(yamlPath, yaml.dump(metaDoc, { indent: 2, quotingType: '"', forceQuotes: false, lineWidth: -1 }), 'utf8');
180
+ const targetFilePath = path.join(mediaDir, `${baseName}${ext}`);
181
+ if (fs.existsSync(draftAbsPath)) {
182
+ if (removeDraft) {
183
+ moveFileSafe(draftAbsPath, targetFilePath);
184
+ }
185
+ else {
186
+ fs.copyFileSync(draftAbsPath, targetFilePath);
187
+ }
188
+ }
189
+ // Retire any earlier id-named mirror of the same record so one asset never occupies two names.
190
+ for (const legacy of names.legacyBaseNames) {
191
+ for (const suffix of [ext, '.yaml']) {
192
+ const legacyPath = path.join(mediaDir, `${legacy}${suffix}`);
193
+ if (legacyPath !== yamlPath && legacyPath !== targetFilePath && fs.existsSync(legacyPath)) {
194
+ try {
195
+ fs.unlinkSync(legacyPath);
196
+ }
197
+ catch { }
198
+ }
199
+ }
200
+ }
201
+ cleanupDuplicateMediaFilesForId(mediaDir, item.id, baseName);
202
+ return {
203
+ filePath: path.posix.join('media', `${baseName}${ext}`),
204
+ markdownRef: path.posix.join('media', baseName),
205
+ };
206
+ }
207
+ //# sourceMappingURL=mirror_item.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror_item.js","sourceRoot":"","sources":["../../../src/sync/media/mirror_item.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { type EmDashMediaItem, type MediaMirrorNames } from './types.js';
2
+ /**
3
+ * Moves a file from src to dest, falling back to copy + unlink when src and dest
4
+ * reside on different filesystems (`EXDEV`).
5
+ */
6
+ export declare function moveFileSafe(src: string, dest: string): void;
7
+ /**
8
+ * Reads the media record id a mirror belongs to from its companion YAML, accepting both the storageKey-named
9
+ * stem and the legacy `<yyyyMMdd>_<id>-<name>` form. Returns `null` when no companion exists or it names no
10
+ * valid ULID, which is how Emit refuses to prune a file it cannot attribute to a record. A companion whose
11
+ * id is explicitly null (or any non-ULID value) proves the mirror backs no real record, so the stem's own
12
+ * leading characters are never trusted as an id — that fallback would let fabricated fixture ids reach
13
+ * record-keyed logic.
14
+ */
15
+ export declare function mediaRecordIdForStem(mediaDir: string, stem: string): string | null;
16
+ /**
17
+ * Removes any stale duplicate files in `media/` that belong to the same 26-char `mediaId` but sit under a
18
+ * different stem — a leftover date-prefixed name from an earlier local promotion, for instance. Ownership is
19
+ * read from each file's companion YAML rather than from the filename, because a storageKey-named mirror
20
+ * carries no record id at all and must never be matched (or deleted) by a coincidental ULID prefix.
21
+ */
22
+ export declare function cleanupDuplicateMediaFilesForId(mediaDir: string, mediaId: string, canonicalBaseName: string): void;
23
+ /**
24
+ * Resolves the storageKey-derived stem against the names already taken in `media/`. Two distinct keys can
25
+ * sanitize to the same basename — `a/sample.png` and `b/sample.png` both yield `sample` — and a second asset
26
+ * must never overwrite the first. A name already owned by this same record is reused as-is, so re-pulling is
27
+ * idempotent; any other collision gets the deterministic `<stem>--<mediaId>` suffix.
28
+ */
29
+ export declare function resolveMarkdownRefCollision(mediaDir: string, baseRef: string, item: EmDashMediaItem): string;
30
+ /**
31
+ * Computes the canonical mirror name for one remote media item. Falls back to the record-id based stem only
32
+ * when the storageKey carries no usable basename, so a malformed migrated key cannot produce an unnameable
33
+ * file. Pass `mediaDir` to resolve a stem collision against the mirrors already on disk.
34
+ */
35
+ export declare function mediaMirrorNames(item: EmDashMediaItem, mediaDir?: string): MediaMirrorNames;
36
+ //# sourceMappingURL=naming_mirror.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming_mirror.d.ts","sourceRoot":"","sources":["../../../src/sync/media/naming_mirror.ts"],"names":[],"mappings":"AASA,OAAO,EAA2B,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAElG;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAW5D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmBlF;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,MAAM,GACxB,IAAI,CAwBN;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,GACpB,MAAM,CAgBR;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAkB3F"}
@@ -0,0 +1,143 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import yaml from 'js-yaml';
4
+ import { formatDatePrefix, sanitizeTitleForFilename, storageKeyToMarkdownRef, collisionSuffixedMarkdownRef, } from '../../utils/frontmatter.js';
5
+ import { MEDIA_BINARY_EXTENSIONS } from './types.js';
6
+ /**
7
+ * Moves a file from src to dest, falling back to copy + unlink when src and dest
8
+ * reside on different filesystems (`EXDEV`).
9
+ */
10
+ export function moveFileSafe(src, dest) {
11
+ try {
12
+ fs.renameSync(src, dest);
13
+ }
14
+ catch (err) {
15
+ if (err?.code === 'EXDEV') {
16
+ fs.copyFileSync(src, dest);
17
+ fs.unlinkSync(src);
18
+ }
19
+ else {
20
+ throw err;
21
+ }
22
+ }
23
+ }
24
+ /**
25
+ * Reads the media record id a mirror belongs to from its companion YAML, accepting both the storageKey-named
26
+ * stem and the legacy `<yyyyMMdd>_<id>-<name>` form. Returns `null` when no companion exists or it names no
27
+ * valid ULID, which is how Emit refuses to prune a file it cannot attribute to a record. A companion whose
28
+ * id is explicitly null (or any non-ULID value) proves the mirror backs no real record, so the stem's own
29
+ * leading characters are never trusted as an id — that fallback would let fabricated fixture ids reach
30
+ * record-keyed logic.
31
+ */
32
+ export function mediaRecordIdForStem(mediaDir, stem) {
33
+ const candidates = [`${stem}.yaml`, `${stem.replace(/^\d{8}_/, '')}.yaml`];
34
+ for (const name of candidates) {
35
+ const candidatePath = path.join(mediaDir, name);
36
+ if (!fs.existsSync(candidatePath))
37
+ continue;
38
+ let doc = null;
39
+ try {
40
+ doc = yaml.load(fs.readFileSync(candidatePath, 'utf8'));
41
+ }
42
+ catch {
43
+ continue;
44
+ }
45
+ if (!doc)
46
+ continue;
47
+ const rawId = typeof doc.id === 'string' ? doc.id.trim() : '';
48
+ if (/^[0-9A-Za-z_-]{26}$/.test(rawId)) {
49
+ return rawId.toUpperCase();
50
+ }
51
+ return null;
52
+ }
53
+ return null;
54
+ }
55
+ /**
56
+ * Removes any stale duplicate files in `media/` that belong to the same 26-char `mediaId` but sit under a
57
+ * different stem — a leftover date-prefixed name from an earlier local promotion, for instance. Ownership is
58
+ * read from each file's companion YAML rather than from the filename, because a storageKey-named mirror
59
+ * carries no record id at all and must never be matched (or deleted) by a coincidental ULID prefix.
60
+ */
61
+ export function cleanupDuplicateMediaFilesForId(mediaDir, mediaId, canonicalBaseName) {
62
+ if (!fs.existsSync(mediaDir))
63
+ return;
64
+ const wanted = mediaId.toUpperCase();
65
+ for (const entry of fs.readdirSync(mediaDir)) {
66
+ if (entry.startsWith('legacy_'))
67
+ continue;
68
+ const entryBase = entry.replace(/\.yaml$/, '').replace(/\.[^.]+$/, '');
69
+ if (entryBase === canonicalBaseName)
70
+ continue;
71
+ // Only a companion-backed mirror can be attributed, and only when the companion itself is being
72
+ // retired together with its binary.
73
+ if (!entry.endsWith('.yaml'))
74
+ continue;
75
+ if (mediaRecordIdForStem(mediaDir, entryBase) !== wanted)
76
+ continue;
77
+ for (const orphan of [
78
+ `${entryBase}.yaml`,
79
+ ...MEDIA_BINARY_EXTENSIONS.map((e) => `${entryBase}${e}`),
80
+ ]) {
81
+ const orphanPath = path.join(mediaDir, orphan);
82
+ if (orphan === `${canonicalBaseName}.yaml`)
83
+ continue;
84
+ try {
85
+ if (fs.existsSync(orphanPath))
86
+ fs.unlinkSync(orphanPath);
87
+ }
88
+ catch {
89
+ // Ignore cleanup errors
90
+ }
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ * Resolves the storageKey-derived stem against the names already taken in `media/`. Two distinct keys can
96
+ * sanitize to the same basename — `a/sample.png` and `b/sample.png` both yield `sample` — and a second asset
97
+ * must never overwrite the first. A name already owned by this same record is reused as-is, so re-pulling is
98
+ * idempotent; any other collision gets the deterministic `<stem>--<mediaId>` suffix.
99
+ */
100
+ export function resolveMarkdownRefCollision(mediaDir, baseRef, item) {
101
+ const suffixed = collisionSuffixedMarkdownRef(baseRef, item.id);
102
+ const takenByOther = (stem) => {
103
+ for (const suffix of ['.yaml', ...MEDIA_BINARY_EXTENSIONS]) {
104
+ const candidatePath = path.join(mediaDir, `${stem}${suffix}`);
105
+ if (!fs.existsSync(candidatePath))
106
+ continue;
107
+ const ownerId = mediaRecordIdForStem(mediaDir, stem);
108
+ if (ownerId && ownerId !== item.id.toUpperCase())
109
+ return true;
110
+ }
111
+ return false;
112
+ };
113
+ if (!takenByOther(baseRef))
114
+ return baseRef;
115
+ if (!takenByOther(suffixed))
116
+ return suffixed;
117
+ // Both taken and both owned by this record: the suffixed form is already this asset's, so keep it.
118
+ return suffixed;
119
+ }
120
+ /**
121
+ * Computes the canonical mirror name for one remote media item. Falls back to the record-id based stem only
122
+ * when the storageKey carries no usable basename, so a malformed migrated key cannot produce an unnameable
123
+ * file. Pass `mediaDir` to resolve a stem collision against the mirrors already on disk.
124
+ */
125
+ export function mediaMirrorNames(item, mediaDir) {
126
+ const ext = path.extname(item.filename) || '';
127
+ const sanitizedBase = sanitizeTitleForFilename(path.basename(item.filename, ext));
128
+ const legacyBaseNames = [
129
+ `${item.id}-${sanitizedBase}`,
130
+ `${formatDatePrefix(item.createdAt)}${item.id}-${sanitizedBase}`,
131
+ ];
132
+ const fromKey = item.storageKey ? storageKeyToMarkdownRef(item.storageKey) : null;
133
+ if (fromKey) {
134
+ const markdownRef = mediaDir ? resolveMarkdownRefCollision(mediaDir, fromKey, item) : fromKey;
135
+ return { markdownRef, ext, legacyBaseNames };
136
+ }
137
+ return {
138
+ markdownRef: `${formatDatePrefix(item.createdAt)}${item.id}-${sanitizedBase}`,
139
+ ext,
140
+ legacyBaseNames,
141
+ };
142
+ }
143
+ //# sourceMappingURL=naming_mirror.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming_mirror.js","sourceRoot":"","sources":["../../../src/sync/media/naming_mirror.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { CacheDatabase } from '../../db/cache.js';
2
+ import type { MirroredAssetResult, PullMediaResult, RemoteStorageKeyIndex } from './types.js';
3
+ /**
4
+ * Pulls media referenced by a specific article (by ULID or file path).
5
+ */
6
+ export declare function pullArticleMedia(options: {
7
+ workspaceRoot: string;
8
+ origin: string;
9
+ token: string;
10
+ refreshToken?: string;
11
+ article: string;
12
+ collection?: string;
13
+ downloadFiles?: boolean;
14
+ fetchFn?: typeof fetch;
15
+ cache?: CacheDatabase;
16
+ }): Promise<PullMediaResult>;
17
+ /**
18
+ * Mirrors only the assets a batch of pulled articles actually references, reusing the records the lightweight
19
+ * index walk already fetched instead of paying for a per-asset `GET /_emdash/api/media/{id}`.
20
+ *
21
+ * This is the mirror half of the default-ON local-first pull: the walk phase builds `RemoteStorageKeyIndex`
22
+ * (JSON only, no binaries), and this phase downloads the binary + companion YAML for exactly the storageKeys
23
+ * found in article bodies. A key the walk did not report is NOT proof of a missing record — the record can
24
+ * exist under a different primary key while the body carries an R2 storageKey — so it is re-checked against
25
+ * confirmed evidence only (a walked record resolved under that exact ULID, or local mirror metadata) and what
26
+ * still fails is reported as `orphanStorageKeys`. Emit never fetches such an asset and never registers one.
27
+ */
28
+ export declare function mirrorReferencedAssetsFromIndex(referencedStorageKeys: Iterable<string>, index: RemoteStorageKeyIndex, options: {
29
+ workspaceRoot: string;
30
+ origin: string;
31
+ token: string;
32
+ refreshToken?: string;
33
+ downloadFiles?: boolean;
34
+ fetchFn?: typeof fetch;
35
+ cache?: CacheDatabase;
36
+ }): Promise<MirroredAssetResult>;
37
+ //# sourceMappingURL=pull_article.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull_article.d.ts","sourceRoot":"","sources":["../../../src/sync/media/pull_article.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAKvD,OAAO,KAAK,EAEV,mBAAmB,EAEnB,eAAe,EACf,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,GAAG,OAAO,CAAC,eAAe,CAAC,CA6L3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,+BAA+B,CACnD,qBAAqB,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvC,KAAK,EAAE,qBAAqB,EAC5B,OAAO,EAAE;IACP,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,GACA,OAAO,CAAC,mBAAmB,CAAC,CAuE9B"}