@xtrable-ltd/nanoesis 0.1.26 → 0.1.28

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 (31) hide show
  1. package/dist/adapter-azure-blob.js +1 -1
  2. package/dist/{chunk-TEIQFPNO.js → chunk-BCWZRKMF.js} +48 -14
  3. package/dist/{chunk-H6ZHZWDJ.js → chunk-GFQT7BYP.js} +209 -96
  4. package/dist/editor-api.d.ts +29 -3
  5. package/dist/editor-api.js +2 -2
  6. package/dist/index.d.ts +107 -2
  7. package/dist/index.js +3 -1
  8. package/dist/mcp.js +3 -3
  9. package/editor/assets/MigrationsPane-CtPr1oUg.js +4 -0
  10. package/editor/assets/{TemplatesPane-Dt8Z7trK.js → TemplatesPane-TAfekASM.js} +116 -116
  11. package/editor/assets/{cssMode-D9n2swjt.js → cssMode-BcU22nqp.js} +1 -1
  12. package/editor/assets/{freemarker2-DPD1J29I.js → freemarker2-B-QbEUNh.js} +1 -1
  13. package/editor/assets/{handlebars-D3A5tmfF.js → handlebars-DV6BwKT-.js} +1 -1
  14. package/editor/assets/{html-ChVj5GP3.js → html-BC8FYPjv.js} +1 -1
  15. package/editor/assets/{htmlMode-BLNw3RHS.js → htmlMode-CuTG0OKP.js} +1 -1
  16. package/editor/assets/index-BGqgRhO7.js +142 -0
  17. package/editor/assets/{index-D4IOSCAV.css → index-CUG-24-D.css} +1 -1
  18. package/editor/assets/{javascript-DNcoKqyy.js → javascript-Dkg4BI3B.js} +1 -1
  19. package/editor/assets/{jsonMode-DMU6Vb_m.js → jsonMode-GEjr-YmZ.js} +1 -1
  20. package/editor/assets/{liquid-BXHGTS2v.js → liquid-Xm_A7c9U.js} +1 -1
  21. package/editor/assets/{mdx-BV8b_dZV.js → mdx-BVQuw7Zm.js} +1 -1
  22. package/editor/assets/{python-C4cGrrAN.js → python-8sgL70mf.js} +1 -1
  23. package/editor/assets/{razor-DW1FX8pl.js → razor-Dc1MxAkI.js} +1 -1
  24. package/editor/assets/{tsMode-C141_BK1.js → tsMode-DHtFLIS4.js} +1 -1
  25. package/editor/assets/{typescript-CakAGQIi.js → typescript-D88EeJzi.js} +1 -1
  26. package/editor/assets/{xml-BbYDGoTr.js → xml-CBE14H0l.js} +1 -1
  27. package/editor/assets/{yaml-CSwnT3wl.js → yaml-OUgHM8CE.js} +1 -1
  28. package/editor/index.html +2 -2
  29. package/package.json +1 -1
  30. package/editor/assets/MigrationsPane-BEY3O03s.js +0 -4
  31. package/editor/assets/index-DbxwBEBR.js +0 -145
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  contentTypeFor
3
- } from "./chunk-H6ZHZWDJ.js";
3
+ } from "./chunk-GFQT7BYP.js";
4
4
 
5
5
  // ../../adapters/azure-blob/src/container.ts
6
6
  var InMemoryBlobContainer = class {
@@ -13,11 +13,12 @@ import {
13
13
  loadComponents,
14
14
  loadTemplate,
15
15
  pendingMigrations,
16
+ planPublish,
16
17
  publishSite,
17
18
  renderReferenceMarkdown,
18
19
  validateSite,
19
20
  workingStoreRoundTripDiagnostic
20
- } from "./chunk-H6ZHZWDJ.js";
21
+ } from "./chunk-GFQT7BYP.js";
21
22
 
22
23
  // ../editor-api/src/scaffold.ts
23
24
  var HOME_HTML = `<!doctype html>
@@ -531,6 +532,20 @@ async function dispatchApi(deps, req) {
531
532
  const status = result.reason === "exists" ? 409 : 404;
532
533
  return json(status, { ok: false, error: result.reason });
533
534
  }
535
+ case "/api/publish/plan": {
536
+ if (req.method !== "GET") return json(405, { ok: false, error: "GET only" });
537
+ return json(200, await planPublish(deps.store));
538
+ }
539
+ case "/api/publish/progress": {
540
+ if (req.method !== "GET") return json(405, { ok: false, error: "GET only" });
541
+ const since = Number.parseInt(get("since"), 10);
542
+ const snapshot = deps.publishProgress?.(Number.isFinite(since) ? since : 0) ?? {
543
+ running: false,
544
+ total: 0,
545
+ events: []
546
+ };
547
+ return json(200, snapshot);
548
+ }
534
549
  case "/api/publish": {
535
550
  if (req.method !== "POST") return methodNotAllowed();
536
551
  const result = await deps.publish();
@@ -923,26 +938,45 @@ function createEditor(config) {
923
938
  const sink = asSink(config.website);
924
939
  const wipeBeforePublish = config.wipeBeforePublish ?? true;
925
940
  const reconcile = config.enumerate === void 0 ? void 0 : async () => working.reconcile([...await config.enumerate()]);
926
- const publish = async () => {
927
- const validation = await validateSite(working);
928
- if (validation.ok && wipeBeforePublish && config.website.wipe !== void 0) {
929
- await config.website.wipe();
941
+ let progressEvents = [];
942
+ let progressRunning = false;
943
+ const publish = async (options) => {
944
+ progressEvents = [];
945
+ progressRunning = true;
946
+ const record = (event) => {
947
+ progressEvents.push(event);
948
+ options?.onProgress?.(event);
949
+ };
950
+ try {
951
+ const validation = await validateSite(working);
952
+ if (validation.ok && wipeBeforePublish && config.website.wipe !== void 0) {
953
+ await config.website.wipe();
954
+ }
955
+ const dir = config.users === void 0 ? void 0 : authorDirectory(await config.users());
956
+ const prebuild = typeof config.prebuild === "function" ? await config.prebuild() : config.prebuild;
957
+ return await publishSite(working, sink, {
958
+ ...config.images !== void 0 && { imageEncoder: config.images },
959
+ ...config.purge !== void 0 && { purge: config.purge },
960
+ ...config.baseUrl !== void 0 && { baseUrl: config.baseUrl },
961
+ ...dir !== void 0 && { authorDirectory: dir },
962
+ ...prebuild !== void 0 && { prebuild },
963
+ onProgress: record
964
+ });
965
+ } finally {
966
+ progressRunning = false;
930
967
  }
931
- const dir = config.users === void 0 ? void 0 : authorDirectory(await config.users());
932
- const prebuild = typeof config.prebuild === "function" ? await config.prebuild() : config.prebuild;
933
- return publishSite(working, sink, {
934
- ...config.images !== void 0 && { imageEncoder: config.images },
935
- ...config.purge !== void 0 && { purge: config.purge },
936
- ...config.baseUrl !== void 0 && { baseUrl: config.baseUrl },
937
- ...dir !== void 0 && { authorDirectory: dir },
938
- ...prebuild !== void 0 && { prebuild }
939
- });
940
968
  };
969
+ const publishProgress = (since) => ({
970
+ running: progressRunning,
971
+ total: progressEvents.length,
972
+ events: progressEvents.slice(Math.max(0, since))
973
+ });
941
974
  const users = config.users;
942
975
  const deps = {
943
976
  store: working,
944
977
  identity: config.login,
945
978
  publish,
979
+ publishProgress,
946
980
  diagnostics: config.diagnostics ?? buildDefaultDiagnostics(),
947
981
  ...reconcile !== void 0 && { reconcile },
948
982
  ...config.authEndpoints !== void 0 && { authEndpoints: config.authEndpoints },
@@ -2052,26 +2052,7 @@ function urlForItem(contentPath) {
2052
2052
 
2053
2053
  // ../engine/src/compile/site.ts
2054
2054
  var DEFAULT_IMAGE_CONCURRENCY = 4;
2055
- async function compileSite(source, options = {}) {
2056
- const [
2057
- tree,
2058
- components,
2059
- componentStyles,
2060
- componentScripts,
2061
- documentShell,
2062
- redirects,
2063
- siteConfig
2064
- ] = await Promise.all([
2065
- loadContentTree(source),
2066
- loadComponents(source),
2067
- loadComponentStyles(source),
2068
- loadComponentScripts(source),
2069
- loadDocumentShell(source),
2070
- loadRedirects(source),
2071
- loadSiteConfig(source)
2072
- ]);
2073
- const context = buildResolveContext(tree);
2074
- const baseUrl = options.baseUrl ?? siteConfig.baseUrl;
2055
+ async function planPages(tree, source, components) {
2075
2056
  const templateCache = /* @__PURE__ */ new Map();
2076
2057
  const getTemplate = async (name) => {
2077
2058
  const cached = templateCache.get(name);
@@ -2080,16 +2061,6 @@ async function compileSite(source, options = {}) {
2080
2061
  templateCache.set(name, loaded);
2081
2062
  return loaded;
2082
2063
  };
2083
- const styleCache = /* @__PURE__ */ new Map();
2084
- const scriptCache = /* @__PURE__ */ new Map();
2085
- const getStyle = async (name) => {
2086
- if (!styleCache.has(name)) styleCache.set(name, await loadTemplateStyle(source, name));
2087
- return styleCache.get(name);
2088
- };
2089
- const getScript = async (name) => {
2090
- if (!scriptCache.has(name)) scriptCache.set(name, await loadTemplateScript(source, name));
2091
- return scriptCache.get(name);
2092
- };
2093
2064
  const analysisCache = /* @__PURE__ */ new Map();
2094
2065
  const getAnalysis = (name, templateSource) => {
2095
2066
  const cached = analysisCache.get(name);
@@ -2098,8 +2069,7 @@ async function compileSite(source, options = {}) {
2098
2069
  analysisCache.set(name, analysis);
2099
2070
  return analysis;
2100
2071
  };
2101
- const artifacts = [];
2102
- const entries = [];
2072
+ const plans = [];
2103
2073
  const walk = async (dir, inheritedTemplate) => {
2104
2074
  const dirTemplate = dir.defaultTemplate ?? inheritedTemplate;
2105
2075
  for (const child of dir.children) {
@@ -2116,34 +2086,126 @@ async function compileSite(source, options = {}) {
2116
2086
  }
2117
2087
  const template = await getTemplate(templateName);
2118
2088
  const analysis = getAnalysis(templateName, template);
2119
- const { media, mediaArtifacts } = await collectMedia(
2120
- source,
2121
- child,
2122
- analysis,
2123
- options.imageEncoder,
2124
- options.imageConcurrency ?? DEFAULT_IMAGE_CONCURRENCY
2125
- );
2126
- artifacts.push(...mediaArtifacts);
2127
- const templateStyle = await getStyle(templateName);
2128
- const templateScript = await getScript(templateName);
2129
- const contents = compileTemplate({
2130
- template,
2131
- scope: buildScope(child.item),
2132
- components,
2133
- context,
2134
- media,
2135
- componentStyles,
2136
- componentScripts,
2137
- ...options.authorDirectory !== void 0 && { authorDirectory: options.authorDirectory },
2138
- ...documentShell !== void 0 && { document: documentShell },
2139
- ...templateStyle !== void 0 && { templateStyle },
2140
- ...templateScript !== void 0 && { templateScript }
2141
- });
2142
- artifacts.push({ path: outputPathForItem(child.path), contents });
2143
- entries.push(toEntry(child));
2089
+ plans.push({ node: child, templateName, template, refs: itemMediaRefs(child, analysis) });
2144
2090
  }
2145
2091
  };
2146
2092
  await walk(tree, void 0);
2093
+ return plans;
2094
+ }
2095
+ function assetLabel(published) {
2096
+ const slash = published.lastIndexOf("/");
2097
+ return slash === -1 ? published : published.slice(slash + 1);
2098
+ }
2099
+ function buildPublishPlan(plans) {
2100
+ const images = [];
2101
+ const files = [];
2102
+ const imageSeen = /* @__PURE__ */ new Set();
2103
+ const fileSeen = /* @__PURE__ */ new Set();
2104
+ for (const plan of plans) {
2105
+ for (const { published } of plan.refs.images) {
2106
+ if (imageSeen.has(published)) continue;
2107
+ imageSeen.add(published);
2108
+ images.push({ kind: "image", id: published, label: assetLabel(published) });
2109
+ }
2110
+ for (const { published } of plan.refs.files) {
2111
+ if (fileSeen.has(published)) continue;
2112
+ fileSeen.add(published);
2113
+ files.push({ kind: "file", id: published, label: assetLabel(published) });
2114
+ }
2115
+ }
2116
+ const pages = plans.map((plan) => ({
2117
+ kind: "page",
2118
+ id: outputPathForItem(plan.node.path),
2119
+ label: plan.node.item.title
2120
+ }));
2121
+ return {
2122
+ resources: [...images, ...files, ...pages],
2123
+ images: images.length,
2124
+ files: files.length,
2125
+ pages: pages.length
2126
+ };
2127
+ }
2128
+ async function planPublish(source) {
2129
+ const [tree, components] = await Promise.all([loadContentTree(source), loadComponents(source)]);
2130
+ return buildPublishPlan(await planPages(tree, source, components));
2131
+ }
2132
+ async function compileSite(source, options = {}) {
2133
+ const [
2134
+ tree,
2135
+ components,
2136
+ componentStyles,
2137
+ componentScripts,
2138
+ documentShell,
2139
+ redirects,
2140
+ siteConfig
2141
+ ] = await Promise.all([
2142
+ loadContentTree(source),
2143
+ loadComponents(source),
2144
+ loadComponentStyles(source),
2145
+ loadComponentScripts(source),
2146
+ loadDocumentShell(source),
2147
+ loadRedirects(source),
2148
+ loadSiteConfig(source)
2149
+ ]);
2150
+ const context = buildResolveContext(tree);
2151
+ const baseUrl = options.baseUrl ?? siteConfig.baseUrl;
2152
+ const styleCache = /* @__PURE__ */ new Map();
2153
+ const scriptCache = /* @__PURE__ */ new Map();
2154
+ const getStyle = async (name) => {
2155
+ if (!styleCache.has(name)) styleCache.set(name, await loadTemplateStyle(source, name));
2156
+ return styleCache.get(name);
2157
+ };
2158
+ const getScript = async (name) => {
2159
+ if (!scriptCache.has(name)) scriptCache.set(name, await loadTemplateScript(source, name));
2160
+ return scriptCache.get(name);
2161
+ };
2162
+ const artifacts = [];
2163
+ const entries = [];
2164
+ const plans = await planPages(tree, source, components);
2165
+ const onProgress = options.onProgress;
2166
+ let done = 0;
2167
+ let resourceById;
2168
+ if (onProgress !== void 0) {
2169
+ const publishPlan = buildPublishPlan(plans);
2170
+ resourceById = new Map(publishPlan.resources.map((resource) => [resource.id, resource]));
2171
+ onProgress({ phase: "plan", plan: publishPlan });
2172
+ }
2173
+ const report = (id) => {
2174
+ if (onProgress === void 0 || resourceById === void 0) return;
2175
+ const resource = resourceById.get(id);
2176
+ if (resource === void 0) return;
2177
+ done += 1;
2178
+ onProgress({ phase: "resource", resource, done, total: resourceById.size });
2179
+ };
2180
+ const { mediaArtifacts, imageInfoByPublished, fileUrlByPublished } = await collectSiteMedia(
2181
+ source,
2182
+ plans.map((p) => p.refs),
2183
+ options.imageEncoder,
2184
+ options.imageConcurrency ?? DEFAULT_IMAGE_CONCURRENCY,
2185
+ onProgress === void 0 ? void 0 : (published) => report(published)
2186
+ );
2187
+ artifacts.push(...mediaArtifacts);
2188
+ for (const { node, templateName, template, refs } of plans) {
2189
+ const templateStyle = await getStyle(templateName);
2190
+ const templateScript = await getScript(templateName);
2191
+ const contents = compileTemplate({
2192
+ template,
2193
+ scope: buildScope(node.item),
2194
+ components,
2195
+ context,
2196
+ media: resolverFor(refs, imageInfoByPublished, fileUrlByPublished),
2197
+ componentStyles,
2198
+ componentScripts,
2199
+ ...options.authorDirectory !== void 0 && { authorDirectory: options.authorDirectory },
2200
+ ...documentShell !== void 0 && { document: documentShell },
2201
+ ...templateStyle !== void 0 && { templateStyle },
2202
+ ...templateScript !== void 0 && { templateScript }
2203
+ });
2204
+ const outputPath = outputPathForItem(node.path);
2205
+ artifacts.push({ path: outputPath, contents });
2206
+ entries.push(toEntry(node));
2207
+ report(outputPath);
2208
+ }
2147
2209
  const redirectArtifact = buildRedirects(redirects, new Set(entries.map((entry) => entry.url)));
2148
2210
  if (redirectArtifact !== void 0) artifacts.push(redirectArtifact);
2149
2211
  if (options.contentIndex !== false) artifacts.push(buildContentIndex(entries));
@@ -2189,14 +2251,14 @@ async function compilePage(source, itemPath, options = {}) {
2189
2251
  if (options.media !== void 0) {
2190
2252
  media = options.media;
2191
2253
  } else {
2192
- const collected = await collectMedia(
2254
+ const refs = itemMediaRefs(node, analysis);
2255
+ const collected = await collectSiteMedia(
2193
2256
  source,
2194
- node,
2195
- analysis,
2257
+ [refs],
2196
2258
  options.imageEncoder,
2197
2259
  DEFAULT_IMAGE_CONCURRENCY
2198
2260
  );
2199
- media = collected.media;
2261
+ media = resolverFor(refs, collected.imageInfoByPublished, collected.fileUrlByPublished);
2200
2262
  mediaArtifacts = collected.mediaArtifacts;
2201
2263
  }
2202
2264
  const html = compileTemplate({
@@ -2244,48 +2306,91 @@ function inCollection(entry, collectionPath) {
2244
2306
  if (collection === "") return true;
2245
2307
  return entry.path === collection || entry.path.startsWith(`${collection}/`);
2246
2308
  }
2247
- async function collectMedia(source, node, analysis, encoder, imageConcurrency) {
2309
+ function itemMediaRefs(node, analysis) {
2248
2310
  const itemDir = parentPath(node.path);
2249
2311
  const fields = node.item.fields;
2250
- const images = /* @__PURE__ */ new Map();
2251
- const files = /* @__PURE__ */ new Map();
2252
- const mediaArtifacts = [];
2312
+ const files = [];
2253
2313
  for (const field of analysis.fileFields) {
2254
2314
  for (const ref of assetRefs(fields[field])) {
2255
- const published = joinAsset(itemDir, ref);
2256
- const bytes = await tryReadBytes(source, `content/${published}`);
2257
- if (bytes === void 0) continue;
2258
- mediaArtifacts.push({ path: published, contents: bytes });
2259
- files.set(ref, `/${published}`);
2315
+ files.push({ ref, published: joinAsset(itemDir, ref) });
2260
2316
  }
2261
2317
  }
2262
- if (encoder !== void 0) {
2263
- const imageRefs = /* @__PURE__ */ new Set();
2264
- for (const field of analysis.imageFields) {
2265
- for (const ref of assetRefs(fields[field])) imageRefs.add(ref);
2266
- }
2267
- for (const field of analysis.richTextFields) {
2268
- const value = fields[field];
2269
- if (typeof value === "string") {
2270
- for (const ref of inlineImageRefs(value)) imageRefs.add(ref);
2318
+ const images = [];
2319
+ const seen = /* @__PURE__ */ new Set();
2320
+ const addImage = (ref) => {
2321
+ if (seen.has(ref)) return;
2322
+ seen.add(ref);
2323
+ images.push({ ref, published: joinAsset(itemDir, ref) });
2324
+ };
2325
+ for (const field of analysis.imageFields) {
2326
+ for (const ref of assetRefs(fields[field])) addImage(ref);
2327
+ }
2328
+ for (const field of analysis.richTextFields) {
2329
+ const value = fields[field];
2330
+ if (typeof value === "string") {
2331
+ for (const ref of inlineImageRefs(value)) addImage(ref);
2332
+ }
2333
+ }
2334
+ return { images, files };
2335
+ }
2336
+ async function collectSiteMedia(source, itemRefs, encoder, imageConcurrency, onAsset) {
2337
+ const uniqueOrdered = (pick) => {
2338
+ const seen = /* @__PURE__ */ new Set();
2339
+ const order = [];
2340
+ for (const refs of itemRefs) {
2341
+ for (const { published } of pick(refs)) {
2342
+ if (!seen.has(published)) {
2343
+ seen.add(published);
2344
+ order.push(published);
2345
+ }
2271
2346
  }
2272
2347
  }
2273
- const encoded = await mapWithConcurrency([...imageRefs], imageConcurrency, async (ref) => {
2274
- const published = joinAsset(itemDir, ref);
2348
+ return order;
2349
+ };
2350
+ const mediaArtifacts = [];
2351
+ const filePaths = uniqueOrdered((r) => r.files);
2352
+ const fileBytes = await mapWithConcurrency(filePaths, imageConcurrency, async (published) => {
2353
+ const bytes = await tryReadBytes(source, `content/${published}`);
2354
+ onAsset?.(published);
2355
+ return bytes;
2356
+ });
2357
+ const fileUrlByPublished = /* @__PURE__ */ new Map();
2358
+ filePaths.forEach((published, index) => {
2359
+ const bytes = fileBytes[index];
2360
+ if (bytes === void 0) return;
2361
+ mediaArtifacts.push({ path: published, contents: bytes });
2362
+ fileUrlByPublished.set(published, `/${published}`);
2363
+ });
2364
+ const imageInfoByPublished = /* @__PURE__ */ new Map();
2365
+ if (encoder !== void 0) {
2366
+ const imagePaths = uniqueOrdered((r) => r.images);
2367
+ const encoded = await mapWithConcurrency(imagePaths, imageConcurrency, async (published) => {
2275
2368
  const bytes = await tryReadBytes(source, `content/${published}`);
2276
- if (bytes === void 0) return void 0;
2277
- return { ref, ...await processImage(bytes, published, encoder) };
2369
+ const result = bytes === void 0 ? void 0 : await processImage(bytes, published, encoder);
2370
+ onAsset?.(published);
2371
+ return result;
2278
2372
  });
2279
- for (const result of encoded) {
2280
- if (result === void 0) continue;
2373
+ imagePaths.forEach((published, index) => {
2374
+ const result = encoded[index];
2375
+ if (result === void 0) return;
2281
2376
  mediaArtifacts.push(...result.artifacts);
2282
- images.set(result.ref, result.info);
2283
- }
2377
+ imageInfoByPublished.set(published, result.info);
2378
+ });
2284
2379
  }
2285
- return {
2286
- media: { image: (ref) => images.get(ref), file: (ref) => files.get(ref) },
2287
- mediaArtifacts
2288
- };
2380
+ return { mediaArtifacts, imageInfoByPublished, fileUrlByPublished };
2381
+ }
2382
+ function resolverFor(refs, imageInfoByPublished, fileUrlByPublished) {
2383
+ const imageByRef = /* @__PURE__ */ new Map();
2384
+ for (const { ref, published } of refs.images) {
2385
+ const info = imageInfoByPublished.get(published);
2386
+ if (info !== void 0) imageByRef.set(ref, info);
2387
+ }
2388
+ const fileByRef = /* @__PURE__ */ new Map();
2389
+ for (const { ref, published } of refs.files) {
2390
+ const url = fileUrlByPublished.get(published);
2391
+ if (url !== void 0) fileByRef.set(ref, url);
2392
+ }
2393
+ return { image: (ref) => imageByRef.get(ref), file: (ref) => fileByRef.get(ref) };
2289
2394
  }
2290
2395
  async function tryReadBytes(source, path) {
2291
2396
  try {
@@ -3078,9 +3183,12 @@ var noopPurgeService = {
3078
3183
  // ../engine/src/publish/publish.ts
3079
3184
  var DEFAULT_WRITE_CONCURRENCY = 8;
3080
3185
  async function publishSite(source, sink, options = {}) {
3186
+ const onProgress = options.onProgress;
3187
+ onProgress?.({ phase: "validate" });
3081
3188
  const validation = await validateSite(source);
3082
3189
  const summary = summarizeTree(await loadContentTree(source));
3083
3190
  if (!validation.ok) {
3191
+ onProgress?.({ phase: "blocked", errors: validation.errors.map((error) => error.message) });
3084
3192
  return { ok: false, validation, written: [], summary };
3085
3193
  }
3086
3194
  const {
@@ -3104,12 +3212,16 @@ async function publishSite(source, sink, options = {}) {
3104
3212
  );
3105
3213
  const byPath = /* @__PURE__ */ new Map();
3106
3214
  for (const artifact of [...stamped, ...passthrough]) byPath.set(artifact.path, artifact);
3107
- await mapWithConcurrency(
3108
- [...byPath.values()],
3109
- writeConcurrency,
3110
- (artifact) => sink.write(artifact.path, artifact.contents, cacheControlFor(artifact))
3111
- );
3215
+ const uploadTotal = byPath.size;
3216
+ let uploaded = 0;
3217
+ await mapWithConcurrency([...byPath.values()], writeConcurrency, async (artifact) => {
3218
+ await sink.write(artifact.path, artifact.contents, cacheControlFor(artifact));
3219
+ uploaded += 1;
3220
+ onProgress?.({ phase: "upload", written: uploaded, total: uploadTotal });
3221
+ });
3222
+ if (purge !== noopPurgeService) onProgress?.({ phase: "purge" });
3112
3223
  await purge.purgeAll();
3224
+ onProgress?.({ phase: "done", written: uploadTotal, summary });
3113
3225
  return { ok: true, validation, written: [...byPath.keys()].sort(), summary };
3114
3226
  }
3115
3227
  function asBytes(contents) {
@@ -3420,6 +3532,7 @@ export {
3420
3532
  buildContentIndex,
3421
3533
  outputPathForItem,
3422
3534
  urlForItem,
3535
+ planPublish,
3423
3536
  compileSite,
3424
3537
  compilePage,
3425
3538
  buildResolveContext,
@@ -1,4 +1,4 @@
1
- import { WorkingStore, IdentityProvider, PublishResult, ReconcileResult, AuthEndpoints, UserAdminEndpoints, AuthorOption, DiagnosticRegistry, Storage, ImageEncoder, PurgeService, UserSummary, PreBuildHook, AuthorDirectory, Repair, DiagnosticCheck } from '@nanoesis/engine';
1
+ import { WorkingStore, IdentityProvider, ProgressReporter, PublishResult, PublishProgress, ReconcileResult, AuthEndpoints, UserAdminEndpoints, AuthorOption, DiagnosticRegistry, Storage, ImageEncoder, PurgeService, UserSummary, PreBuildHook, AuthorDirectory, Repair, DiagnosticCheck } from '@nanoesis/engine';
2
2
  export { Storage } from '@nanoesis/engine';
3
3
 
4
4
  /**
@@ -94,13 +94,39 @@ interface ApiResponse {
94
94
  readonly headers?: Record<string, string>;
95
95
  readonly body?: string | Uint8Array;
96
96
  }
97
+ /**
98
+ * A snapshot of the publish progress log from a cursor (DESIGN §11): the events a poller
99
+ * has not seen yet (`events`, those at index `since` onward), the running total so the
100
+ * client can advance its cursor, and whether a publish is still in flight. The editor polls
101
+ * `GET /api/publish/progress` for this and folds the events into its timeline. This is the
102
+ * progress transport for hosts that cannot stream an HTTP response (Azure Consumption).
103
+ */
104
+ interface PublishProgressLog {
105
+ readonly running: boolean;
106
+ readonly total: number;
107
+ readonly events: readonly PublishProgress[];
108
+ }
97
109
  interface ApiDeps {
98
110
  /** The editor working store (read + write/delete/rename), any {@link WorkingStore}. */
99
111
  readonly store: WorkingStore;
100
112
  /** Who is calling, gates every editing route (DESIGN §11). */
101
113
  readonly identity: IdentityProvider;
102
- /** Run the publish pipeline (host binds source/sink/purge). */
103
- readonly publish: () => Promise<PublishResult>;
114
+ /**
115
+ * Run the publish pipeline (host binds source/sink/purge). The optional `onProgress`
116
+ * callback receives {@link PublishProgress} events as the publish runs; a caller that
117
+ * ignores it (the MCP publish tool, tests) still gets the buffered {@link PublishResult}.
118
+ */
119
+ readonly publish: (options?: {
120
+ onProgress?: ProgressReporter;
121
+ }) => Promise<PublishResult>;
122
+ /**
123
+ * Optional live-progress snapshot for the editor's publish timeline (DESIGN §11). The
124
+ * running publish records its events; `GET /api/publish/progress?since=N` returns the
125
+ * events from cursor `N` plus whether it is still running, so the client polls instead of
126
+ * relying on HTTP streaming (which Azure Consumption does not support). Omitted on a host
127
+ * that does not track progress; the route then reports an idle log.
128
+ */
129
+ readonly publishProgress?: (since: number) => PublishProgressLog;
104
130
  /**
105
131
  * Optional index-reconcile (DESIGN §11d): rebuild the working store's content index
106
132
  * from the keys actually present, recovering files that arrived by a path that
@@ -21,8 +21,8 @@ import {
21
21
  serveEditorAsset,
22
22
  templateSnapshotIntegrityDiagnostic,
23
23
  templateSuffixConflictDiagnostic
24
- } from "./chunk-TEIQFPNO.js";
25
- import "./chunk-H6ZHZWDJ.js";
24
+ } from "./chunk-BCWZRKMF.js";
25
+ import "./chunk-GFQT7BYP.js";
26
26
  export {
27
27
  FileBrandingStore,
28
28
  InMemoryBrandingStore,