emdash-emit 0.1.18 → 0.1.30

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 (237) hide show
  1. package/CHANGELOG.md +249 -1
  2. package/README.md +22 -11
  3. package/dist/cli/commands/conflicts.d.ts.map +1 -1
  4. package/dist/cli/commands/conflicts.js +2 -22
  5. package/dist/cli/commands/conflicts.js.map +1 -1
  6. package/dist/cli/commands/delete.d.ts.map +1 -1
  7. package/dist/cli/commands/delete.js +1 -13
  8. package/dist/cli/commands/delete.js.map +1 -1
  9. package/dist/cli/commands/doctor.d.ts.map +1 -1
  10. package/dist/cli/commands/doctor.js +1 -13
  11. package/dist/cli/commands/doctor.js.map +1 -1
  12. package/dist/cli/commands/media.d.ts.map +1 -1
  13. package/dist/cli/commands/media.js +1 -13
  14. package/dist/cli/commands/media.js.map +1 -1
  15. package/dist/cli/commands/new.d.ts.map +1 -1
  16. package/dist/cli/commands/new.js +31 -13
  17. package/dist/cli/commands/new.js.map +1 -1
  18. package/dist/cli/commands/pull.d.ts +1 -1
  19. package/dist/cli/commands/pull.d.ts.map +1 -1
  20. package/dist/cli/commands/pull.js +110 -79
  21. package/dist/cli/commands/pull.js.map +1 -1
  22. package/dist/cli/commands/push.d.ts +1 -1
  23. package/dist/cli/commands/push.d.ts.map +1 -1
  24. package/dist/cli/commands/push.js +32 -90
  25. package/dist/cli/commands/push.js.map +1 -1
  26. package/dist/cli/commands/status.d.ts.map +1 -1
  27. package/dist/cli/commands/status.js +7 -2
  28. package/dist/cli/commands/status.js.map +1 -1
  29. package/dist/cli/context.d.ts +12 -0
  30. package/dist/cli/context.d.ts.map +1 -1
  31. package/dist/cli/context.js +27 -1
  32. package/dist/cli/context.js.map +1 -1
  33. package/dist/cli/report-failure.d.ts +6 -0
  34. package/dist/cli/report-failure.d.ts.map +1 -0
  35. package/dist/cli/report-failure.js +71 -0
  36. package/dist/cli/report-failure.js.map +1 -0
  37. package/dist/client/api.d.ts +20 -0
  38. package/dist/client/api.d.ts.map +1 -1
  39. package/dist/client/api.js +90 -1
  40. package/dist/client/api.js.map +1 -1
  41. package/dist/client/lock.js.map +1 -1
  42. package/dist/client/mapper.js.map +1 -1
  43. package/dist/client/media.d.ts.map +1 -1
  44. package/dist/client/media.js.map +1 -1
  45. package/dist/db/baseline.d.ts +19 -3
  46. package/dist/db/baseline.d.ts.map +1 -1
  47. package/dist/db/baseline.js +27 -2
  48. package/dist/db/baseline.js.map +1 -1
  49. package/dist/db/cache.d.ts +7 -3
  50. package/dist/db/cache.d.ts.map +1 -1
  51. package/dist/db/cache.js +24 -295
  52. package/dist/db/cache.js.map +1 -1
  53. package/dist/db/migrations.d.ts +7 -0
  54. package/dist/db/migrations.d.ts.map +1 -0
  55. package/dist/db/migrations.js +320 -0
  56. package/dist/db/migrations.js.map +1 -0
  57. package/dist/mcp/heartbeat.d.ts +1 -34
  58. package/dist/mcp/heartbeat.d.ts.map +1 -1
  59. package/dist/mcp/heartbeat.js +1 -113
  60. package/dist/mcp/heartbeat.js.map +1 -1
  61. package/dist/mcp/tool-errors.d.ts +12 -1
  62. package/dist/mcp/tool-errors.d.ts.map +1 -1
  63. package/dist/mcp/tool-errors.js +29 -0
  64. package/dist/mcp/tool-errors.js.map +1 -1
  65. package/dist/mcp/tools/sync-conflicts.d.ts.map +1 -1
  66. package/dist/mcp/tools/sync-conflicts.js +46 -58
  67. package/dist/mcp/tools/sync-conflicts.js.map +1 -1
  68. package/dist/mcp/tools/sync-delete.d.ts.map +1 -1
  69. package/dist/mcp/tools/sync-delete.js +18 -30
  70. package/dist/mcp/tools/sync-delete.js.map +1 -1
  71. package/dist/mcp/tools/sync-diff.d.ts.map +1 -1
  72. package/dist/mcp/tools/sync-diff.js +10 -31
  73. package/dist/mcp/tools/sync-diff.js.map +1 -1
  74. package/dist/mcp/tools/sync-doctor.d.ts.map +1 -1
  75. package/dist/mcp/tools/sync-doctor.js +18 -30
  76. package/dist/mcp/tools/sync-doctor.js.map +1 -1
  77. package/dist/mcp/tools/sync-media-search.d.ts.map +1 -1
  78. package/dist/mcp/tools/sync-media-search.js +40 -52
  79. package/dist/mcp/tools/sync-media-search.js.map +1 -1
  80. package/dist/mcp/tools/sync-new.d.ts.map +1 -1
  81. package/dist/mcp/tools/sync-new.js +54 -34
  82. package/dist/mcp/tools/sync-new.js.map +1 -1
  83. package/dist/mcp/tools/sync-pull.d.ts.map +1 -1
  84. package/dist/mcp/tools/sync-pull.js +194 -220
  85. package/dist/mcp/tools/sync-pull.js.map +1 -1
  86. package/dist/mcp/tools/sync-push.d.ts.map +1 -1
  87. package/dist/mcp/tools/sync-push.js +64 -84
  88. package/dist/mcp/tools/sync-push.js.map +1 -1
  89. package/dist/mcp/tools/sync-status.d.ts.map +1 -1
  90. package/dist/mcp/tools/sync-status.js +4 -13
  91. package/dist/mcp/tools/sync-status.js.map +1 -1
  92. package/dist/sync/baseline-reconstruction.d.ts +43 -0
  93. package/dist/sync/baseline-reconstruction.d.ts.map +1 -0
  94. package/dist/sync/baseline-reconstruction.js +103 -0
  95. package/dist/sync/baseline-reconstruction.js.map +1 -0
  96. package/dist/sync/bylines.d.ts +5 -0
  97. package/dist/sync/bylines.d.ts.map +1 -1
  98. package/dist/sync/bylines.js +29 -0
  99. package/dist/sync/bylines.js.map +1 -1
  100. package/dist/sync/conflicts.d.ts.map +1 -1
  101. package/dist/sync/conflicts.js +21 -4
  102. package/dist/sync/conflicts.js.map +1 -1
  103. package/dist/sync/delete.d.ts.map +1 -1
  104. package/dist/sync/delete.js +12 -1
  105. package/dist/sync/delete.js.map +1 -1
  106. package/dist/sync/diff.d.ts.map +1 -1
  107. package/dist/sync/diff.js +62 -26
  108. package/dist/sync/diff.js.map +1 -1
  109. package/dist/sync/doctor.d.ts.map +1 -1
  110. package/dist/sync/doctor.js +13 -3
  111. package/dist/sync/doctor.js.map +1 -1
  112. package/dist/sync/guards.d.ts +1 -1
  113. package/dist/sync/guards.d.ts.map +1 -1
  114. package/dist/sync/guards.js +3 -2
  115. package/dist/sync/guards.js.map +1 -1
  116. package/dist/sync/media/companions.d.ts.map +1 -1
  117. package/dist/sync/media/companions.js.map +1 -1
  118. package/dist/sync/media/naming_mirror.d.ts.map +1 -1
  119. package/dist/sync/media/naming_mirror.js +11 -2
  120. package/dist/sync/media/naming_mirror.js.map +1 -1
  121. package/dist/sync/media/pull_article.d.ts +2 -0
  122. package/dist/sync/media/pull_article.d.ts.map +1 -1
  123. package/dist/sync/media/pull_article.js +74 -25
  124. package/dist/sync/media/pull_article.js.map +1 -1
  125. package/dist/sync/media/pull_library.d.ts.map +1 -1
  126. package/dist/sync/media/pull_library.js +34 -61
  127. package/dist/sync/media/pull_library.js.map +1 -1
  128. package/dist/sync/media/remote_index.d.ts +16 -1
  129. package/dist/sync/media/remote_index.d.ts.map +1 -1
  130. package/dist/sync/media/remote_index.js +2 -2
  131. package/dist/sync/media/remote_index.js.map +1 -1
  132. package/dist/sync/merge.d.ts +1 -1
  133. package/dist/sync/merge.d.ts.map +1 -1
  134. package/dist/sync/merge.js +50 -17
  135. package/dist/sync/merge.js.map +1 -1
  136. package/dist/sync/new.d.ts +11 -0
  137. package/dist/sync/new.d.ts.map +1 -1
  138. package/dist/sync/new.js +81 -3
  139. package/dist/sync/new.js.map +1 -1
  140. package/dist/sync/orchestrator.d.ts +34 -0
  141. package/dist/sync/orchestrator.d.ts.map +1 -1
  142. package/dist/sync/orchestrator.js +89 -80
  143. package/dist/sync/orchestrator.js.map +1 -1
  144. package/dist/sync/preflight/image_probe.d.ts +28 -0
  145. package/dist/sync/preflight/image_probe.d.ts.map +1 -0
  146. package/dist/sync/preflight/image_probe.js +70 -0
  147. package/dist/sync/preflight/image_probe.js.map +1 -0
  148. package/dist/sync/preflight.d.ts +7 -0
  149. package/dist/sync/preflight.d.ts.map +1 -1
  150. package/dist/sync/preflight.js +127 -31
  151. package/dist/sync/preflight.js.map +1 -1
  152. package/dist/sync/pull/fast_path.d.ts +9 -0
  153. package/dist/sync/pull/fast_path.d.ts.map +1 -0
  154. package/dist/sync/pull/fast_path.js +341 -0
  155. package/dist/sync/pull/fast_path.js.map +1 -0
  156. package/dist/sync/pull/index.d.ts +15 -0
  157. package/dist/sync/pull/index.d.ts.map +1 -0
  158. package/dist/sync/pull/index.js +363 -0
  159. package/dist/sync/pull/index.js.map +1 -0
  160. package/dist/sync/pull/list.d.ts +22 -0
  161. package/dist/sync/pull/list.d.ts.map +1 -0
  162. package/dist/sync/pull/list.js +175 -0
  163. package/dist/sync/pull/list.js.map +1 -0
  164. package/dist/sync/pull/media_stage.d.ts +67 -0
  165. package/dist/sync/pull/media_stage.d.ts.map +1 -0
  166. package/dist/sync/pull/media_stage.js +599 -0
  167. package/dist/sync/pull/media_stage.js.map +1 -0
  168. package/dist/sync/pull/quarantine.d.ts +22 -0
  169. package/dist/sync/pull/quarantine.d.ts.map +1 -0
  170. package/dist/sync/pull/quarantine.js +174 -0
  171. package/dist/sync/pull/quarantine.js.map +1 -0
  172. package/dist/sync/pull/types.d.ts +125 -0
  173. package/dist/sync/pull/types.d.ts.map +1 -0
  174. package/dist/sync/pull/types.js +23 -0
  175. package/dist/sync/pull/types.js.map +1 -0
  176. package/dist/sync/pull.d.ts +1 -114
  177. package/dist/sync/pull.d.ts.map +1 -1
  178. package/dist/sync/pull.js +1 -806
  179. package/dist/sync/pull.js.map +1 -1
  180. package/dist/sync/push/commit.d.ts +1 -0
  181. package/dist/sync/push/commit.d.ts.map +1 -1
  182. package/dist/sync/push/commit.js +58 -31
  183. package/dist/sync/push/commit.js.map +1 -1
  184. package/dist/sync/push/index.d.ts.map +1 -1
  185. package/dist/sync/push/index.js +30 -8
  186. package/dist/sync/push/index.js.map +1 -1
  187. package/dist/sync/push/media_stage.d.ts +17 -2
  188. package/dist/sync/push/media_stage.d.ts.map +1 -1
  189. package/dist/sync/push/media_stage.js +31 -2
  190. package/dist/sync/push/media_stage.js.map +1 -1
  191. package/dist/sync/push/merge_stage.d.ts.map +1 -1
  192. package/dist/sync/push/merge_stage.js +36 -22
  193. package/dist/sync/push/merge_stage.js.map +1 -1
  194. package/dist/sync/push/transport.d.ts +6 -2
  195. package/dist/sync/push/transport.d.ts.map +1 -1
  196. package/dist/sync/push/transport.js +5 -5
  197. package/dist/sync/push/transport.js.map +1 -1
  198. package/dist/sync/status.d.ts +3 -0
  199. package/dist/sync/status.d.ts.map +1 -1
  200. package/dist/sync/status.js +18 -55
  201. package/dist/sync/status.js.map +1 -1
  202. package/dist/sync/taxonomies.d.ts +25 -0
  203. package/dist/sync/taxonomies.d.ts.map +1 -1
  204. package/dist/sync/taxonomies.js +180 -127
  205. package/dist/sync/taxonomies.js.map +1 -1
  206. package/dist/sync/workspace/commit.d.ts +46 -0
  207. package/dist/sync/workspace/commit.d.ts.map +1 -0
  208. package/dist/sync/workspace/commit.js +85 -0
  209. package/dist/sync/workspace/commit.js.map +1 -0
  210. package/dist/sync/workspace/index.d.ts +1 -0
  211. package/dist/sync/workspace/index.d.ts.map +1 -1
  212. package/dist/sync/workspace/index.js +1 -0
  213. package/dist/sync/workspace/index.js.map +1 -1
  214. package/dist/utils/disk-spelling.d.ts +26 -0
  215. package/dist/utils/disk-spelling.d.ts.map +1 -0
  216. package/dist/utils/disk-spelling.js +51 -0
  217. package/dist/utils/disk-spelling.js.map +1 -0
  218. package/dist/utils/draft-revision.d.ts +15 -0
  219. package/dist/utils/draft-revision.d.ts.map +1 -0
  220. package/dist/utils/draft-revision.js +36 -0
  221. package/dist/utils/draft-revision.js.map +1 -0
  222. package/dist/utils/frontmatter.d.ts +21 -2
  223. package/dist/utils/frontmatter.d.ts.map +1 -1
  224. package/dist/utils/frontmatter.js +70 -7
  225. package/dist/utils/frontmatter.js.map +1 -1
  226. package/dist/utils/mcp-heartbeat.d.ts +36 -0
  227. package/dist/utils/mcp-heartbeat.d.ts.map +1 -0
  228. package/dist/utils/mcp-heartbeat.js +126 -0
  229. package/dist/utils/mcp-heartbeat.js.map +1 -0
  230. package/dist/utils/media-body-refs.d.ts.map +1 -1
  231. package/dist/utils/media-body-refs.js +2 -2
  232. package/dist/utils/media-body-refs.js.map +1 -1
  233. package/dist/utils/taxonomy-key.d.ts +25 -0
  234. package/dist/utils/taxonomy-key.d.ts.map +1 -0
  235. package/dist/utils/taxonomy-key.js +51 -0
  236. package/dist/utils/taxonomy-key.js.map +1 -0
  237. package/package.json +1 -1
@@ -0,0 +1,67 @@
1
+ import type { CacheDatabase } from '../../db/cache.js';
2
+ import { type LocalMediaMirrorCandidate, type RemoteStorageKeyIndex } from '../media.js';
3
+ import type { MediaMirrorSession, PullArticleMediaSummary, PullOptions } from './types.js';
4
+ export interface MediaMirrorSessionOptions {
5
+ workspaceRoot: string;
6
+ origin: string;
7
+ token: string;
8
+ refreshToken?: string;
9
+ cache?: CacheDatabase;
10
+ fetchFn?: typeof fetch;
11
+ downloadFiles?: boolean;
12
+ isSharedSession?: boolean;
13
+ /** Bounds the shared library walk. Defaults to `mediaSyncLimit`; `all: true` removes the bound. */
14
+ limit?: number;
15
+ all?: boolean;
16
+ }
17
+ export declare class MediaMirrorSessionImpl implements MediaMirrorSession {
18
+ private readonly options;
19
+ private readonly mediaDir;
20
+ private readonly isSharedSession;
21
+ private remoteIndexPromise;
22
+ private localCandidatesMap;
23
+ private readonly inFlightAssets;
24
+ private readonly sessionMirroredStorageKeys;
25
+ private readonly sessionVerifiedStorageKeys;
26
+ private readonly sessionOrphanStorageKeys;
27
+ private sessionDownloaded;
28
+ private sessionSkipped;
29
+ constructor(options: MediaMirrorSessionOptions);
30
+ /**
31
+ * The shared library walk, bounded by `maxItems` (default `mediaSyncLimit`, unlimited for `--all`) exactly
32
+ * like the library pass. A batch pull against a fleet-scale library must never pay for an unbounded scan on
33
+ * behalf of a handful of articles; a key that lands outside the window is reported as unresolved rather than
34
+ * asserted absent, and `complete === false` keeps every per-record lookup available downstream.
35
+ */
36
+ getRemoteIndex(): Promise<RemoteStorageKeyIndex>;
37
+ getLocalCandidates(): Map<string, LocalMediaMirrorCandidate>;
38
+ /**
39
+ * Mirrors media referenced by an article (both body storageKeys and frontmatter featuredImage).
40
+ */
41
+ mirrorArticleReferencedMedia(params: {
42
+ canonicalMarkdown: string;
43
+ featuredImage?: string | null;
44
+ }): Promise<PullArticleMediaSummary | undefined>;
45
+ /**
46
+ * Finalizes media statistics for pullBatch.
47
+ */
48
+ finalizeBatchMediaResult(): Promise<{
49
+ walkedCount: number;
50
+ remoteTotalCount: number | null;
51
+ complete: boolean;
52
+ downloaded: number;
53
+ skipped: number;
54
+ mirroredStorageKeys: string[];
55
+ verifiedStorageKeys: string[];
56
+ orphanStorageKeys: string[];
57
+ }>;
58
+ }
59
+ /**
60
+ * Creates a shared MediaMirrorSession.
61
+ */
62
+ export declare function createMediaMirrorSession(options: MediaMirrorSessionOptions): MediaMirrorSession;
63
+ /**
64
+ * Convenience helper to mirror referenced media for a single article.
65
+ */
66
+ export declare function mirrorArticleReferencedMedia(options: PullOptions, canonicalMarkdown: string, featuredImage?: string | null): Promise<PullArticleMediaSummary | undefined>;
67
+ //# sourceMappingURL=media_stage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media_stage.d.ts","sourceRoot":"","sources":["../../../src/sync/pull/media_stage.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAUvD,OAAO,EAQL,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE3F,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mGAAmG;IACnG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAqGD,qBAAa,sBAAuB,YAAW,kBAAkB;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,kBAAkB,CAA+C;IACzE,OAAO,CAAC,kBAAkB,CAAuD;IACjF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAkD;IAGjF,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAqB;IAChE,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAqB;IAChE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAqB;IAC9D,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAAK;IAE3B,YAAY,OAAO,EAAE,yBAAyB,EAI7C;IAED;;;;;OAKG;IACI,cAAc,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAetD;IAEM,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAKlE;IAED;;OAEG;IACU,4BAA4B,CAAC,MAAM,EAAE;QAChD,iBAAiB,EAAE,MAAM,CAAC;QAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,GAAG,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CA0a/C;IAED;;OAEG;IACU,wBAAwB,IAAI,OAAO,CAAC;QAC/C,WAAW,EAAE,MAAM,CAAC;QACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;QAChC,QAAQ,EAAE,OAAO,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,mBAAmB,EAAE,MAAM,EAAE,CAAC;QAC9B,mBAAmB,EAAE,MAAM,EAAE,CAAC;QAC9B,iBAAiB,EAAE,MAAM,EAAE,CAAC;KAC7B,CAAC,CAYD;CACF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CAE/F;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,WAAW,EACpB,iBAAiB,EAAE,MAAM,EACzB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5B,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CAkB9C"}
@@ -0,0 +1,599 @@
1
+ /*
2
+ * Copyright (C) 2026 SPORT-SERVICE RS★R
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import fs from 'node:fs';
23
+ import path from 'node:path';
24
+ import crypto from 'node:crypto';
25
+ import yaml from 'js-yaml';
26
+ import { createAuthedFetch } from '../../client/http.js';
27
+ import { unwrapEnvelope } from '../../client/api.js';
28
+ import { mediaDetailEndpoint } from '../../client/endpoints.js';
29
+ import { extractMediaUlid } from '../../client/mapper.js';
30
+ import { DEFAULT_CONFIG } from '../../utils/config.js';
31
+ import { toMediaRecordId, asMediaRecordId, toStorageKey, asStorageKey, } from '../../utils/media-id.js';
32
+ import { MEDIA_FILE_ROUTE_PREFIX } from '../../utils/media-refs.js';
33
+ import { storageKeyToMarkdownRef } from '../../utils/naming.js';
34
+ import { collectArticleStorageKeys, collectRemoteStorageKeys, collectLocalMediaMirrorCandidates, syncSingleMediaItem, mediaRecordIdForStem, MEDIA_BINARY_EXTENSIONS, } from '../media.js';
35
+ function computeFileSha256(filePath) {
36
+ const buffer = fs.readFileSync(filePath);
37
+ return crypto.createHash('sha256').update(buffer).digest('hex');
38
+ }
39
+ /**
40
+ * Finds a local binary file matching a storageKey, using media_state workspacePath if available,
41
+ * or searching media/ directory for markdownRef.<ext>.
42
+ */
43
+ function findLocalBinaryForStorageKey(mediaDir, storageKey, cache, workspaceRoot) {
44
+ if (cache && workspaceRoot) {
45
+ const state = cache.getMediaStateByStorageKey(storageKey);
46
+ if (state?.workspacePath) {
47
+ const full = path.join(workspaceRoot, state.workspacePath);
48
+ if (fs.existsSync(full)) {
49
+ const ext = path.extname(state.workspacePath);
50
+ const stem = path.basename(state.markdownRef || state.workspacePath, ext);
51
+ return { binaryPath: full, stem };
52
+ }
53
+ }
54
+ }
55
+ if (!fs.existsSync(mediaDir))
56
+ return null;
57
+ const ref = storageKeyToMarkdownRef(storageKey) || storageKey;
58
+ const ext = path.extname(storageKey);
59
+ if (ext) {
60
+ const candidate = path.join(mediaDir, `${ref}${ext}`);
61
+ if (fs.existsSync(candidate))
62
+ return { binaryPath: candidate, stem: ref };
63
+ }
64
+ for (const bExt of MEDIA_BINARY_EXTENSIONS) {
65
+ const candidate = path.join(mediaDir, `${ref}${bExt}`);
66
+ if (fs.existsSync(candidate))
67
+ return { binaryPath: candidate, stem: ref };
68
+ }
69
+ for (const file of fs.readdirSync(mediaDir)) {
70
+ if (file.endsWith('.yaml') || file.startsWith('legacy_'))
71
+ continue;
72
+ const fileStem = file.replace(/\.[^.]+$/, '');
73
+ if (fileStem === ref) {
74
+ return { binaryPath: path.join(mediaDir, file), stem: ref };
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+ /**
80
+ * Finds a local binary matching a mediaId ULID (used for featuredImage).
81
+ */
82
+ function findLocalBinaryForMediaId(mediaDir, mediaId, cache, workspaceRoot) {
83
+ const wanted = mediaId.toUpperCase();
84
+ if (cache && workspaceRoot) {
85
+ const state = cache.getMediaStateByMediaId(wanted);
86
+ if (state?.workspacePath) {
87
+ const full = path.join(workspaceRoot, state.workspacePath);
88
+ if (fs.existsSync(full)) {
89
+ const ext = path.extname(state.workspacePath);
90
+ const stem = path.basename(state.markdownRef || state.workspacePath, ext);
91
+ return { binaryPath: full, stem, storageKey: state.storageKey || undefined };
92
+ }
93
+ }
94
+ }
95
+ if (!fs.existsSync(mediaDir))
96
+ return null;
97
+ for (const file of fs.readdirSync(mediaDir)) {
98
+ if (!file.endsWith('.yaml') || file.startsWith('legacy_'))
99
+ continue;
100
+ const stem = file.replace(/\.yaml$/, '');
101
+ if (mediaRecordIdForStem(mediaDir, stem) === wanted) {
102
+ for (const ext of MEDIA_BINARY_EXTENSIONS) {
103
+ const candidate = path.join(mediaDir, `${stem}${ext}`);
104
+ if (fs.existsSync(candidate)) {
105
+ return { binaryPath: candidate, stem };
106
+ }
107
+ }
108
+ }
109
+ }
110
+ return null;
111
+ }
112
+ export class MediaMirrorSessionImpl {
113
+ options;
114
+ mediaDir;
115
+ isSharedSession;
116
+ remoteIndexPromise = null;
117
+ localCandidatesMap = null;
118
+ inFlightAssets = new Map();
119
+ // Aggregate session statistics (for pullBatch)
120
+ sessionMirroredStorageKeys = new Set();
121
+ sessionVerifiedStorageKeys = new Set();
122
+ sessionOrphanStorageKeys = new Set();
123
+ sessionDownloaded = 0;
124
+ sessionSkipped = 0;
125
+ constructor(options) {
126
+ this.options = options;
127
+ this.mediaDir = path.join(options.workspaceRoot, 'media');
128
+ this.isSharedSession = options.isSharedSession === true;
129
+ }
130
+ /**
131
+ * The shared library walk, bounded by `maxItems` (default `mediaSyncLimit`, unlimited for `--all`) exactly
132
+ * like the library pass. A batch pull against a fleet-scale library must never pay for an unbounded scan on
133
+ * behalf of a handful of articles; a key that lands outside the window is reported as unresolved rather than
134
+ * asserted absent, and `complete === false` keeps every per-record lookup available downstream.
135
+ */
136
+ getRemoteIndex() {
137
+ if (!this.remoteIndexPromise) {
138
+ this.remoteIndexPromise = collectRemoteStorageKeys({
139
+ workspaceRoot: this.options.workspaceRoot,
140
+ origin: this.options.origin,
141
+ token: this.options.token,
142
+ refreshToken: this.options.refreshToken,
143
+ maxItems: this.options.all === true || typeof this.options.limit === 'number'
144
+ ? this.options.limit
145
+ : DEFAULT_CONFIG.mediaSyncLimit,
146
+ fetchFn: this.options.fetchFn,
147
+ });
148
+ }
149
+ return this.remoteIndexPromise;
150
+ }
151
+ getLocalCandidates() {
152
+ if (!this.localCandidatesMap) {
153
+ this.localCandidatesMap = collectLocalMediaMirrorCandidates(this.mediaDir);
154
+ }
155
+ return this.localCandidatesMap;
156
+ }
157
+ /**
158
+ * Mirrors media referenced by an article (both body storageKeys and frontmatter featuredImage).
159
+ */
160
+ async mirrorArticleReferencedMedia(params) {
161
+ const { canonicalMarkdown, featuredImage } = params;
162
+ // Step 1: Extract references
163
+ const bodyDescriptors = collectArticleStorageKeys(canonicalMarkdown);
164
+ const bodyStorageKeys = Array.from(new Set(bodyDescriptors.map((d) => d.storageKey)));
165
+ const featuredUlid = featuredImage ? extractMediaUlid(featuredImage) : null;
166
+ const featuredMediaId = featuredUlid ? featuredUlid.toUpperCase() : null;
167
+ if (bodyStorageKeys.length === 0 && !featuredMediaId) {
168
+ return undefined;
169
+ }
170
+ const { workspaceRoot, origin, token, cache, downloadFiles } = this.options;
171
+ const fetchFn = createAuthedFetch(this.options);
172
+ const shouldDownload = downloadFiles !== false;
173
+ // Step 2: 0-HTTP short-circuit inspection (when no remote index is needed yet)
174
+ // Check if all referenced assets are fully mirrored locally.
175
+ const isAssetFullyMirrored = (keyOrId, isFeatured) => {
176
+ if (isFeatured) {
177
+ const found = findLocalBinaryForMediaId(this.mediaDir, keyOrId, cache, workspaceRoot);
178
+ if (!found)
179
+ return { fullyMirrored: false };
180
+ const yamlPath = path.join(this.mediaDir, `${found.stem}.yaml`);
181
+ let doc = null;
182
+ if (fs.existsSync(yamlPath)) {
183
+ try {
184
+ doc = yaml.load(fs.readFileSync(yamlPath, 'utf8'));
185
+ }
186
+ catch { }
187
+ }
188
+ if (typeof doc?.size === 'number') {
189
+ try {
190
+ if (fs.statSync(found.binaryPath).size !== doc.size)
191
+ return { fullyMirrored: false };
192
+ }
193
+ catch {
194
+ return { fullyMirrored: false };
195
+ }
196
+ }
197
+ const state = cache?.getMediaStateByMediaId(keyOrId);
198
+ const companionUlid = mediaRecordIdForStem(this.mediaDir, found.stem);
199
+ if (!state && !companionUlid)
200
+ return { fullyMirrored: false };
201
+ const resolvedMediaId = state?.mediaId || companionUlid;
202
+ const resolvedStorageKey = found.storageKey ||
203
+ state?.storageKey ||
204
+ (typeof doc?.storageKey === 'string' ? doc.storageKey : undefined);
205
+ return {
206
+ fullyMirrored: true,
207
+ binaryPath: found.binaryPath,
208
+ stem: found.stem,
209
+ mediaId: resolvedMediaId,
210
+ storageKey: resolvedStorageKey,
211
+ };
212
+ }
213
+ else {
214
+ const found = findLocalBinaryForStorageKey(this.mediaDir, keyOrId, cache, workspaceRoot);
215
+ if (!found)
216
+ return { fullyMirrored: false };
217
+ const yamlPath = path.join(this.mediaDir, `${found.stem}.yaml`);
218
+ let doc = null;
219
+ if (fs.existsSync(yamlPath)) {
220
+ try {
221
+ doc = yaml.load(fs.readFileSync(yamlPath, 'utf8'));
222
+ }
223
+ catch { }
224
+ }
225
+ if (typeof doc?.size === 'number') {
226
+ try {
227
+ if (fs.statSync(found.binaryPath).size !== doc.size)
228
+ return { fullyMirrored: false };
229
+ }
230
+ catch {
231
+ return { fullyMirrored: false };
232
+ }
233
+ }
234
+ const state = cache?.getMediaStateByStorageKey(keyOrId);
235
+ const companionUlid = mediaRecordIdForStem(this.mediaDir, found.stem);
236
+ if (!state && !companionUlid)
237
+ return { fullyMirrored: false };
238
+ const resolvedMediaId = state?.mediaId || companionUlid;
239
+ return {
240
+ fullyMirrored: true,
241
+ binaryPath: found.binaryPath,
242
+ stem: found.stem,
243
+ mediaId: resolvedMediaId,
244
+ storageKey: keyOrId,
245
+ };
246
+ }
247
+ };
248
+ const bodyCheck = bodyStorageKeys.map((k) => ({
249
+ key: k,
250
+ ...isAssetFullyMirrored(k, false),
251
+ }));
252
+ const featuredCheck = featuredMediaId
253
+ ? { id: featuredMediaId, ...isAssetFullyMirrored(featuredMediaId, true) }
254
+ : null;
255
+ const allFullyMirrored = bodyCheck.every((b) => b.fullyMirrored) && (!featuredCheck || featuredCheck.fullyMirrored);
256
+ if (allFullyMirrored) {
257
+ // 0-HTTP short-circuit: all referenced assets already exist locally.
258
+ // Upsert media_state for any entries missing localContentHash or unrecorded in DB.
259
+ if (cache) {
260
+ const allItems = [...bodyCheck];
261
+ if (featuredCheck) {
262
+ allItems.push({
263
+ key: featuredCheck.id,
264
+ fullyMirrored: true,
265
+ binaryPath: featuredCheck.binaryPath,
266
+ stem: featuredCheck.stem,
267
+ mediaId: featuredCheck.mediaId,
268
+ storageKey: featuredCheck.storageKey,
269
+ });
270
+ }
271
+ for (const item of allItems) {
272
+ if (!item.binaryPath || !item.stem || !item.mediaId)
273
+ continue;
274
+ const targetKey = item.storageKey || item.key;
275
+ const brandedStorageKey = toStorageKey(targetKey) ?? asStorageKey(targetKey);
276
+ const existingState = cache.getMediaStateByStorageKey(brandedStorageKey);
277
+ let currentContentHash = existingState?.localContentHash;
278
+ if (!existingState || !currentContentHash) {
279
+ try {
280
+ currentContentHash = computeFileSha256(item.binaryPath);
281
+ }
282
+ catch {
283
+ continue;
284
+ }
285
+ }
286
+ const confirmedMediaId = existingState?.mediaId || item.mediaId;
287
+ const ext = path.extname(item.binaryPath);
288
+ cache.saveMediaState({
289
+ localHash: `media-record:${confirmedMediaId}`,
290
+ localContentHash: currentContentHash,
291
+ mediaId: toMediaRecordId(confirmedMediaId) ?? asMediaRecordId(confirmedMediaId),
292
+ storageKey: brandedStorageKey,
293
+ url: `${MEDIA_FILE_ROUTE_PREFIX}${targetKey}`,
294
+ workspacePath: path.posix.join('media', `${item.stem}${ext}`),
295
+ markdownRef: path.posix.join('media', item.stem),
296
+ status: 'ready',
297
+ remoteRecordRef: `media-record:${confirmedMediaId}`,
298
+ });
299
+ }
300
+ }
301
+ const totalCount = bodyStorageKeys.length + (featuredMediaId ? 1 : 0);
302
+ for (const k of bodyStorageKeys) {
303
+ this.sessionVerifiedStorageKeys.add(k);
304
+ }
305
+ this.sessionSkipped += totalCount;
306
+ return {
307
+ total: totalCount,
308
+ downloaded: 0,
309
+ skipped: totalCount,
310
+ mirroredStorageKeys: [],
311
+ verifiedStorageKeys: [...bodyStorageKeys],
312
+ orphanStorageKeys: [],
313
+ };
314
+ }
315
+ // Step 3: Remote resolution with in-flight deduplication and fallback handling
316
+ fs.mkdirSync(this.mediaDir, { recursive: true });
317
+ let downloadedCount = 0;
318
+ let skippedCount = 0;
319
+ const articleMirrored = new Set();
320
+ const articleVerified = new Set();
321
+ const articleOrphans = new Set();
322
+ let unresolvedFeaturedImageId = undefined;
323
+ let mediaFetchFailed = false;
324
+ // Helper to mirror an asset through in-flight deduplication
325
+ const runOrJoinInFlight = (assetKey, operation) => {
326
+ let inflight = this.inFlightAssets.get(assetKey);
327
+ if (!inflight) {
328
+ inflight = operation().then((outcome) => {
329
+ if (outcome.downloaded) {
330
+ this.sessionDownloaded++;
331
+ }
332
+ else if (outcome.skipped) {
333
+ this.sessionSkipped++;
334
+ }
335
+ return outcome;
336
+ });
337
+ this.inFlightAssets.set(assetKey, inflight);
338
+ }
339
+ return inflight;
340
+ };
341
+ // Case B: Single-article pull (not shared session) with no body storageKeys and only featuredImage is missing
342
+ if (!this.isSharedSession && bodyStorageKeys.length === 0 && featuredMediaId) {
343
+ const outcome = await runOrJoinInFlight(`featured:${featuredMediaId}`, async () => {
344
+ try {
345
+ const detailUrl = mediaDetailEndpoint(origin, featuredMediaId);
346
+ const res = await fetchFn(detailUrl, {
347
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
348
+ });
349
+ if (!res.ok) {
350
+ return { downloaded: false, skipped: false, failed: true };
351
+ }
352
+ const json = await res.json();
353
+ const envelope = unwrapEnvelope(json);
354
+ const item = (envelope.item || envelope);
355
+ const syncRes = await syncSingleMediaItem({
356
+ item,
357
+ mediaDir: this.mediaDir,
358
+ origin,
359
+ token,
360
+ downloadFiles: shouldDownload,
361
+ fetchFn,
362
+ cache,
363
+ });
364
+ return {
365
+ downloaded: syncRes.downloaded,
366
+ skipped: syncRes.skipped,
367
+ failed: false,
368
+ storageKey: item.storageKey,
369
+ };
370
+ }
371
+ catch {
372
+ return { downloaded: false, skipped: false, failed: true };
373
+ }
374
+ });
375
+ if (outcome.downloaded) {
376
+ downloadedCount++;
377
+ }
378
+ else if (outcome.skipped) {
379
+ skippedCount++;
380
+ }
381
+ if (outcome.failed) {
382
+ mediaFetchFailed = true;
383
+ // Non-destructive fallback check: if binary already exists locally, don't mark as unresolved
384
+ const localCheck = isAssetFullyMirrored(featuredMediaId, true);
385
+ if (!localCheck.fullyMirrored) {
386
+ unresolvedFeaturedImageId = featuredMediaId;
387
+ }
388
+ }
389
+ else if (outcome.storageKey) {
390
+ articleMirrored.add(outcome.storageKey);
391
+ this.sessionMirroredStorageKeys.add(outcome.storageKey);
392
+ }
393
+ return {
394
+ total: 1,
395
+ downloaded: downloadedCount,
396
+ skipped: skippedCount,
397
+ mirroredStorageKeys: Array.from(articleMirrored),
398
+ verifiedStorageKeys: [],
399
+ orphanStorageKeys: [],
400
+ unresolvedFeaturedImageId,
401
+ mediaFetchFailed: mediaFetchFailed ? true : undefined,
402
+ };
403
+ }
404
+ // Case A: Body storageKeys present (or shared batch session)
405
+ let remoteIndex = null;
406
+ try {
407
+ remoteIndex = await this.getRemoteIndex();
408
+ }
409
+ catch {
410
+ mediaFetchFailed = true;
411
+ }
412
+ const localCandidates = this.getLocalCandidates();
413
+ // 1. Process body storageKeys
414
+ for (const key of bodyStorageKeys) {
415
+ const outcome = await runOrJoinInFlight(`storageKey:${key}`, async () => {
416
+ let item = remoteIndex?.recordsByStorageKey.get(key);
417
+ if (!item && remoteIndex) {
418
+ const ulids = (key.match(/[0-9A-HJ-NP-Z]{26}/gi) ?? []).map((u) => u.toUpperCase());
419
+ for (const ulid of ulids) {
420
+ const byId = remoteIndex.recordsById.get(ulid);
421
+ if (byId) {
422
+ item = byId;
423
+ break;
424
+ }
425
+ }
426
+ }
427
+ if (item) {
428
+ try {
429
+ const syncRes = await syncSingleMediaItem({
430
+ item,
431
+ mediaDir: this.mediaDir,
432
+ origin,
433
+ token,
434
+ downloadFiles: shouldDownload,
435
+ fetchFn,
436
+ cache,
437
+ });
438
+ return {
439
+ downloaded: syncRes.downloaded,
440
+ skipped: syncRes.skipped,
441
+ failed: false,
442
+ storageKey: item.storageKey || key,
443
+ };
444
+ }
445
+ catch {
446
+ return { downloaded: false, skipped: false, failed: true, storageKey: key };
447
+ }
448
+ }
449
+ return { downloaded: false, skipped: false, failed: true, storageKey: key };
450
+ });
451
+ if (outcome.downloaded) {
452
+ downloadedCount++;
453
+ }
454
+ else if (outcome.skipped) {
455
+ skippedCount++;
456
+ }
457
+ if (!outcome.failed) {
458
+ if (outcome.storageKey) {
459
+ articleMirrored.add(outcome.storageKey);
460
+ this.sessionMirroredStorageKeys.add(outcome.storageKey);
461
+ }
462
+ articleMirrored.add(key);
463
+ this.sessionMirroredStorageKeys.add(key);
464
+ }
465
+ else {
466
+ // Fallback evaluation: does local binary exist or is it confirmed in candidates/index?
467
+ const ulids = (key.match(/[0-9A-HJ-NP-Z]{26}/gi) ?? []).map((u) => u.toUpperCase());
468
+ const hasBinary = findLocalBinaryForStorageKey(this.mediaDir, key, cache, workspaceRoot) !== null;
469
+ const confirmed = hasBinary ||
470
+ (remoteIndex && remoteIndex.storageKeys.has(key)) ||
471
+ ulids.some((u) => remoteIndex?.recordsById.has(u) || localCandidates.has(u));
472
+ if (confirmed) {
473
+ articleVerified.add(key);
474
+ this.sessionVerifiedStorageKeys.add(key);
475
+ }
476
+ else {
477
+ articleOrphans.add(key);
478
+ this.sessionOrphanStorageKeys.add(key);
479
+ mediaFetchFailed = true;
480
+ }
481
+ }
482
+ }
483
+ // 2. Process featuredImage if present
484
+ if (featuredMediaId) {
485
+ const outcome = await runOrJoinInFlight(`featured:${featuredMediaId}`, async () => {
486
+ let item = remoteIndex?.recordsById.get(featuredMediaId);
487
+ // Only attempt individual GET if the full remote walk didn't already exhaustively cover the library
488
+ if (!item && (!remoteIndex || !remoteIndex.complete)) {
489
+ try {
490
+ const detailUrl = mediaDetailEndpoint(origin, featuredMediaId);
491
+ const res = await fetchFn(detailUrl, {
492
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
493
+ });
494
+ if (res.ok) {
495
+ const json = await res.json();
496
+ const envelope = unwrapEnvelope(json);
497
+ item = (envelope.item || envelope);
498
+ }
499
+ }
500
+ catch { }
501
+ }
502
+ if (item) {
503
+ try {
504
+ const syncRes = await syncSingleMediaItem({
505
+ item,
506
+ mediaDir: this.mediaDir,
507
+ origin,
508
+ token,
509
+ downloadFiles: shouldDownload,
510
+ fetchFn,
511
+ cache,
512
+ });
513
+ return {
514
+ downloaded: syncRes.downloaded,
515
+ skipped: syncRes.skipped,
516
+ failed: false,
517
+ storageKey: item.storageKey,
518
+ };
519
+ }
520
+ catch {
521
+ return { downloaded: false, skipped: false, failed: true };
522
+ }
523
+ }
524
+ return { downloaded: false, skipped: false, failed: true };
525
+ });
526
+ if (outcome.downloaded) {
527
+ downloadedCount++;
528
+ }
529
+ else if (outcome.skipped) {
530
+ skippedCount++;
531
+ }
532
+ if (outcome.failed) {
533
+ const localCheck = isAssetFullyMirrored(featuredMediaId, true);
534
+ if (!localCheck.fullyMirrored) {
535
+ unresolvedFeaturedImageId = featuredMediaId;
536
+ mediaFetchFailed = true;
537
+ }
538
+ }
539
+ else if (outcome.storageKey) {
540
+ articleMirrored.add(outcome.storageKey);
541
+ this.sessionMirroredStorageKeys.add(outcome.storageKey);
542
+ }
543
+ }
544
+ const totalProcessed = bodyStorageKeys.length + (featuredMediaId ? 1 : 0);
545
+ return {
546
+ total: totalProcessed,
547
+ downloaded: downloadedCount,
548
+ skipped: skippedCount,
549
+ mirroredStorageKeys: Array.from(articleMirrored),
550
+ verifiedStorageKeys: Array.from(articleVerified),
551
+ orphanStorageKeys: Array.from(articleOrphans),
552
+ unresolvedFeaturedImageId,
553
+ mediaFetchFailed: mediaFetchFailed ? true : undefined,
554
+ };
555
+ }
556
+ /**
557
+ * Finalizes media statistics for pullBatch.
558
+ */
559
+ async finalizeBatchMediaResult() {
560
+ const remoteIndex = await this.getRemoteIndex();
561
+ return {
562
+ walkedCount: remoteIndex.scannedCount,
563
+ remoteTotalCount: remoteIndex.totalCount,
564
+ complete: remoteIndex.complete,
565
+ downloaded: this.sessionDownloaded,
566
+ skipped: this.sessionSkipped,
567
+ mirroredStorageKeys: Array.from(this.sessionMirroredStorageKeys),
568
+ verifiedStorageKeys: Array.from(this.sessionVerifiedStorageKeys),
569
+ orphanStorageKeys: Array.from(this.sessionOrphanStorageKeys),
570
+ };
571
+ }
572
+ }
573
+ /**
574
+ * Creates a shared MediaMirrorSession.
575
+ */
576
+ export function createMediaMirrorSession(options) {
577
+ return new MediaMirrorSessionImpl(options);
578
+ }
579
+ /**
580
+ * Convenience helper to mirror referenced media for a single article.
581
+ */
582
+ export async function mirrorArticleReferencedMedia(options, canonicalMarkdown, featuredImage) {
583
+ const session = options.mediaSession ??
584
+ createMediaMirrorSession({
585
+ workspaceRoot: options.workspaceRoot,
586
+ origin: options.origin,
587
+ token: options.token,
588
+ refreshToken: options.refreshToken,
589
+ cache: options.cache,
590
+ fetchFn: options.fetchFn,
591
+ downloadFiles: true,
592
+ limit: options.mediaSyncLimit,
593
+ });
594
+ return session.mirrorArticleReferencedMedia({
595
+ canonicalMarkdown,
596
+ featuredImage,
597
+ });
598
+ }
599
+ //# sourceMappingURL=media_stage.js.map